From 3ca99751939bc638955cecc2d696d789024cf206 Mon Sep 17 00:00:00 2001 From: Iota <47987888+IotaBread@users.noreply.github.com> Date: Wed, 10 Jan 2024 00:50:30 -0300 Subject: [PATCH] Cleanup the constant fields name finder (renamed from enum fields) (#9) * Rename enum_fields to constant_fields * Move some code to `#stringToUpperSnakeCase` * Organize imports * Rename remaining "enum fields" instances * Move some code to instance methods & fields * More cleaning up * Search field names recursively * Search names and fields passed to constructors * Remove some duplicate code * Fix an exception when reloading the jar * Fix linking to unnamed fields * Fix names for enum fields with overridden methods * Tweak snake case conversion * Fix some unwanted cases --- .../org/quiltmc/enigma_plugin/Arguments.java | 2 +- .../enigma_plugin/index/JarIndexer.java | 22 +- .../ConstantFieldIndex.java} | 14 +- .../ConstantFieldNameFinder.java | 321 ++++++++++++++++++ .../index/enum_fields/FieldNameFinder.java | 235 ------------- ...er.java => ConstantFieldNameProposer.java} | 16 +- .../proposal/NameProposerService.java | 2 +- testProject/profile.json | 4 +- .../com/example/field_names/ClassTest.java | 8 + .../com/example/field_names/Enum2Test.java | 35 ++ 10 files changed, 398 insertions(+), 261 deletions(-) rename src/main/java/org/quiltmc/enigma_plugin/index/{enum_fields/EnumFieldsIndex.java => constant_fields/ConstantFieldIndex.java} (86%) create mode 100644 src/main/java/org/quiltmc/enigma_plugin/index/constant_fields/ConstantFieldNameFinder.java delete mode 100644 src/main/java/org/quiltmc/enigma_plugin/index/enum_fields/FieldNameFinder.java rename src/main/java/org/quiltmc/enigma_plugin/proposal/{EnumFieldNameProposer.java => ConstantFieldNameProposer.java} (72%) create mode 100644 testProject/src/main/java/com/example/field_names/Enum2Test.java diff --git a/src/main/java/org/quiltmc/enigma_plugin/Arguments.java b/src/main/java/org/quiltmc/enigma_plugin/Arguments.java index 5f6d462..8e6cdc8 100644 --- a/src/main/java/org/quiltmc/enigma_plugin/Arguments.java +++ b/src/main/java/org/quiltmc/enigma_plugin/Arguments.java @@ -24,7 +24,7 @@ */ public class Arguments { public static final String DISABLE_RECORDS = "disable_records"; - public static final String DISABLE_ENUM_FIELDS = "disable_enum_fields"; + public static final String DISABLE_CONSTANT_FIELDS = "disable_constant_fields"; public static final String DISABLE_EQUALS = "disable_equals"; public static final String DISABLE_LOGGER = "disable_logger"; public static final String DISABLE_CONSTRUCTOR_PARAMS = "disable_constructor_params"; diff --git a/src/main/java/org/quiltmc/enigma_plugin/index/JarIndexer.java b/src/main/java/org/quiltmc/enigma_plugin/index/JarIndexer.java index dd22463..65ab2be 100644 --- a/src/main/java/org/quiltmc/enigma_plugin/index/JarIndexer.java +++ b/src/main/java/org/quiltmc/enigma_plugin/index/JarIndexer.java @@ -24,7 +24,7 @@ import org.objectweb.asm.tree.ClassNode; import org.quiltmc.enigma_plugin.Arguments; import org.quiltmc.enigma_plugin.QuiltEnigmaPlugin; -import org.quiltmc.enigma_plugin.index.enum_fields.EnumFieldsIndex; +import org.quiltmc.enigma_plugin.index.constant_fields.ConstantFieldIndex; import org.quiltmc.enigma_plugin.index.simple_type_single.SimpleTypeSingleIndex; import java.util.List; @@ -32,14 +32,14 @@ public class JarIndexer implements JarIndexerService, Opcodes { private final RecordIndex recordIndex = new RecordIndex(); - private final EnumFieldsIndex enumFieldsIndex = new EnumFieldsIndex(); + private final ConstantFieldIndex constantFieldIndex = new ConstantFieldIndex(); private final CodecIndex codecIndex = new CodecIndex(); private final LoggerIndex loggerIndex = new LoggerIndex(); private final SimpleTypeSingleIndex simpleTypeSingleIndex = new SimpleTypeSingleIndex(); private final ConstructorParametersIndex constructorParametersIndex = new ConstructorParametersIndex(); private final GetterSetterIndex getterSetterIndex = new GetterSetterIndex(); private boolean disableRecordIndexing = false; - private boolean disableEnumFieldsIndexing = false; + private boolean disableConstantFieldIndexing = false; private boolean disableCodecsIndexing = false; private boolean disableLoggerIndexing = false; private boolean disableConstructorParametersIndexing = false; @@ -47,7 +47,7 @@ public class JarIndexer implements JarIndexerService, Opcodes { public JarIndexer withContext(EnigmaServiceContext context) { this.disableRecordIndexing = Arguments.isDisabled(context, Arguments.DISABLE_RECORDS); - this.disableEnumFieldsIndexing = Arguments.isDisabled(context, Arguments.DISABLE_ENUM_FIELDS); + this.disableConstantFieldIndexing = Arguments.isDisabled(context, Arguments.DISABLE_CONSTANT_FIELDS); this.disableCodecsIndexing = Arguments.isDisabled(context, Arguments.DISABLE_CODECS); this.disableLoggerIndexing = Arguments.isDisabled(context, Arguments.DISABLE_LOGGER); this.disableConstructorParametersIndexing = Arguments.isDisabled(context, Arguments.DISABLE_CONSTRUCTOR_PARAMS); @@ -64,6 +64,8 @@ public JarIndexer withContext(EnigmaServiceContext context) { @Override public void acceptJar(Set scope, ClassProvider classProvider, JarIndex jarIndex) { + this.constantFieldIndex.clear(); + for (String className : scope) { ClassNode node = classProvider.get(className); if (node != null) { @@ -72,8 +74,8 @@ public void acceptJar(Set scope, ClassProvider classProvider, JarIndex j } } - if (!this.disableEnumFieldsIndexing) { - this.enumFieldsIndex.findFieldNames(); + if (!this.disableConstantFieldIndexing) { + this.constantFieldIndex.findFieldNames(); } this.simpleTypeSingleIndex.dropCache(); @@ -88,8 +90,8 @@ private void visitClassNode(ClassNode node) { } } - if (!this.disableEnumFieldsIndexing) { - this.enumFieldsIndex.visitClassNode(node); + if (!this.disableConstantFieldIndexing) { + this.constantFieldIndex.visitClassNode(node); } if (!this.disableCodecsIndexing) { @@ -113,8 +115,8 @@ public RecordIndex getRecordIndex() { return this.recordIndex; } - public EnumFieldsIndex getEnumFieldsIndex() { - return this.enumFieldsIndex; + public ConstantFieldIndex getConstantFieldIndex() { + return this.constantFieldIndex; } public CodecIndex getCodecIndex() { diff --git a/src/main/java/org/quiltmc/enigma_plugin/index/enum_fields/EnumFieldsIndex.java b/src/main/java/org/quiltmc/enigma_plugin/index/constant_fields/ConstantFieldIndex.java similarity index 86% rename from src/main/java/org/quiltmc/enigma_plugin/index/enum_fields/EnumFieldsIndex.java rename to src/main/java/org/quiltmc/enigma_plugin/index/constant_fields/ConstantFieldIndex.java index 0eb9cb5..a7da7fc 100644 --- a/src/main/java/org/quiltmc/enigma_plugin/index/enum_fields/EnumFieldsIndex.java +++ b/src/main/java/org/quiltmc/enigma_plugin/index/constant_fields/ConstantFieldIndex.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.quiltmc.enigma_plugin.index.enum_fields; +package org.quiltmc.enigma_plugin.index.constant_fields; import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; import org.objectweb.asm.tree.ClassNode; @@ -29,7 +29,7 @@ import java.util.Map; import java.util.Set; -public class EnumFieldsIndex implements Index { +public class ConstantFieldIndex implements Index { private final Map> enumFields = new HashMap<>(); private final Map> staticInitializers = new HashMap<>(); private Map fieldNames; @@ -39,7 +39,7 @@ public void visitClassNode(ClassNode node) { for (FieldNode field : node.fields) { if ((field.access & ACC_ENUM) != 0) { if (!this.enumFields.computeIfAbsent(node.name, k -> new HashSet<>()).add(field.name + ":" + field.desc)) { - throw new IllegalStateException("Found a duplicate enum field with name \"" + field.name + "\""); + throw new IllegalStateException("Found a duplicate enum field with name \"" + field.name + "\" in class " + node.name); } } } @@ -53,12 +53,18 @@ public void visitClassNode(ClassNode node) { public void findFieldNames() { try { - this.fieldNames = new FieldNameFinder().findNames(this); + this.fieldNames = new ConstantFieldNameFinder().findNames(this); } catch (Exception e) { throw new RuntimeException(e); } } + public void clear() { + this.enumFields.clear(); + this.staticInitializers.clear(); + this.fieldNames = null; + } + public boolean hasName(FieldEntry field) { return this.fieldNames.containsKey(field); } diff --git a/src/main/java/org/quiltmc/enigma_plugin/index/constant_fields/ConstantFieldNameFinder.java b/src/main/java/org/quiltmc/enigma_plugin/index/constant_fields/ConstantFieldNameFinder.java new file mode 100644 index 0000000..9d754b2 --- /dev/null +++ b/src/main/java/org/quiltmc/enigma_plugin/index/constant_fields/ConstantFieldNameFinder.java @@ -0,0 +1,321 @@ +/* + * Copyright 2016, 2017, 2018, 2019 FabricMC + * Copyright 2022 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.enigma_plugin.index.constant_fields; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.analysis.Analyzer; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.Frame; +import org.objectweb.asm.tree.analysis.SourceInterpreter; +import org.objectweb.asm.tree.analysis.SourceValue; +import org.quiltmc.enigma.api.translation.representation.TypeDescriptor; +import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; +import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; +import org.tinylog.Logger; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; + +public class ConstantFieldNameFinder implements Opcodes { + private final HashMap> usedNamesByClass = new HashMap<>(); + private final HashMap> duplicatedNamesByClass = new HashMap<>(); + private final HashMap linkedFields = new HashMap<>(); + + private static boolean isClassPutStatic(String owner, AbstractInsnNode insn) { + return insn.getOpcode() == PUTSTATIC && ((FieldInsnNode) insn).owner.equals(owner); + } + + private static boolean isInit(AbstractInsnNode insn) { + return insn.getOpcode() == INVOKESPECIAL && ((MethodInsnNode) insn).name.equals(""); + } + + private static boolean isCharacterUsable(char c) { + return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_'; + } + + private static String processIdentifierPath(String name) { + if (name == null) { + return null; + } + + // Remove identifier namespace + if (name.contains(":")) { + name = name.substring(name.lastIndexOf(":") + 1); + } + + // Process a path + if (name.contains("/")) { + int separator = name.indexOf("/"); + String first = name.substring(0, separator); + String last; + + if (name.contains(".") && name.indexOf(".") > separator) { + last = name.substring(separator + 1, name.indexOf(".")); + } else { + last = name.substring(separator + 1); + } + + if (first.endsWith("s")) { + first = first.substring(0, first.length() - 1); + } + + name = last + "_" + first; + } + + return name; + } + + private static String stringToUpperSnakeCase(String s) { + StringBuilder usableName = new StringBuilder(); + boolean hasAlphabetic = false; + boolean prevUsable = false; + + for (int j = 0; j < s.length(); j++) { + char c = s.charAt(j); + + if (isCharacterUsable(c)) { + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + hasAlphabetic = true; + } + + // Add an underscore before if the current character follows another letter/number and is the start of a camel cased word + if (j > 0 && Character.isUpperCase(c) && j < s.length() - 1 && Character.isLowerCase(s.charAt(j + 1)) && prevUsable) { + usableName.append('_'); + } + + usableName.append(Character.toUpperCase(c)); + prevUsable = true; + } else if (j > 0 && j < s.length() - 1 && prevUsable) { + // Replace unusable characters with underscores if they aren't at the start or end, and are following another usable character + usableName.append('_'); + prevUsable = false; + } + } + + if (!hasAlphabetic) { + return null; + } + + return usableName.toString().toUpperCase(); + } + + private static FieldEntry fieldFromInsn(FieldInsnNode insn) { + return new FieldEntry(new ClassEntry(insn.owner), insn.name, new TypeDescriptor(insn.desc)); + } + + private static AbstractInsnNode searchInsnInStack(InsnList insns, AbstractInsnNode frameInsn, Frame[] frames, Predicate insnPredicate) { + int frameIndex = insns.indexOf(frameInsn); + var frame = frames[frameIndex]; + + AbstractInsnNode lastStackInsn = null; + for (int i = 0; i < frame.getStackSize(); i++) { + var value = frame.getStack(i); + for (var stackInsn : value.insns) { + if (insnPredicate.test(stackInsn)) { + return stackInsn; + } else if (stackInsn.getOpcode() == INVOKESTATIC) { + if (!(frameInsn instanceof MethodInsnNode mInsn) || mInsn.owner.equals(((MethodInsnNode) stackInsn).owner)) { + return searchInsnInStack(insns, stackInsn, frames, insnPredicate); + } + } + + lastStackInsn = stackInsn; + } + } + + if (lastStackInsn != null && lastStackInsn.getOpcode() == NEW && lastStackInsn.getNext() != null && lastStackInsn.getNext().getOpcode() == DUP) { + // Find the last frame containing the DUP instruction + var dup = lastStackInsn.getNext(); + int searchFrameIndex = insns.indexOf(dup) + 1; + var searchFrame = frames[searchFrameIndex]; + + while (searchFrame != null && searchFrameIndex <= frameIndex) { + boolean contains = false; + for (int j = 0; j < searchFrame.getStackSize(); j++) { + if (frame.getStack(j).insns.contains(dup)) { + contains = true; + break; + } + } + + if (contains) { + searchFrameIndex++; + if (searchFrameIndex < frames.length) { + searchFrame = frames[searchFrameIndex]; + } else { + searchFrame = null; + } + } else { + var insn = insns.get(searchFrameIndex - 1); // This was the last instruction with a frame with a dup + if (insn != frameInsn) { + return searchInsnInStack(insns, insn, frames, insnPredicate); + } + } + } + } + + return null; + } + + private static String searchStringCstInStack(InsnList insns, AbstractInsnNode frameInsn, Frame[] frames) { + var insn = searchInsnInStack(insns, frameInsn, frames, + insnNode -> insnNode instanceof LdcInsnNode ldc && ldc.cst instanceof String constant && !constant.isBlank()); + if (insn instanceof LdcInsnNode ldc) { + return (String) ldc.cst; + } + + return null; + } + + private static FieldInsnNode searchFieldReferenceInStack(InsnList insns, AbstractInsnNode frameInsn, Frame[] frames, String clazz) { + var insn = searchInsnInStack(insns, frameInsn, frames, + insnNode -> insnNode instanceof FieldInsnNode fieldInsn && fieldInsn.getOpcode() == GETSTATIC && !fieldInsn.owner.equals(clazz)); + if (insn instanceof FieldInsnNode fieldInsn) { + return fieldInsn; + } + + return null; + } + + private FieldEntry followFieldLink(FieldEntry field, Map names) { + if (names.containsKey(field)) { + return field; + } else if (this.linkedFields.containsKey(field)) { + return this.followFieldLink(this.linkedFields.get(field), names); + } + + return null; + } + + private void clear() { + this.usedNamesByClass.clear(); + this.duplicatedNamesByClass.clear(); + this.linkedFields.clear(); + } + + public Map findNames(ConstantFieldIndex fieldIndex) throws Exception { + this.clear(); + + Analyzer analyzer = new Analyzer<>(new SourceInterpreter()); + Map fieldNames = new HashMap<>(); + + for (String clazz : fieldIndex.getStaticInitializers().keySet()) { + var initializers = fieldIndex.getStaticInitializers().get(clazz); + var enumFields = fieldIndex.getEnumFields().getOrDefault(clazz, Collections.emptySet()); + + this.findNamesInInitializers(clazz, initializers, analyzer, fieldNames, enumFields); + } + + // Insert linked names + for (FieldEntry linked : this.linkedFields.keySet()) { + FieldEntry target = this.followFieldLink(linked, fieldNames); + if (target == null) { + continue; + } + + String name = fieldNames.get(target); + if (name == null) { + continue; + } + + String clazz = linked.getParent().getFullName(); + Set usedNames = this.usedNamesByClass.computeIfAbsent(clazz, s -> new HashSet<>()); + Set duplicatedNames = this.duplicatedNamesByClass.computeIfAbsent(clazz, s -> new HashSet<>()); + if (!duplicatedNames.contains(name) && usedNames.add(name)) { + fieldNames.put(linked, name); + } else { + duplicatedNames.add(name); + Logger.warn("Duplicate name \"{}\" for field {}, linked to {}", name, linked, target); + } + } + + return fieldNames; + } + + private void findNamesInInitializers(String clazz, List initializers, Analyzer analyzer, Map fieldNames, Set enumFields) throws AnalyzerException { + var usedNames = this.usedNamesByClass.computeIfAbsent(clazz, s -> new HashSet<>()); + var duplicatedNames = this.duplicatedNamesByClass.computeIfAbsent(clazz, s -> new HashSet<>()); + + for (var initializer : initializers) { + var frames = analyzer.analyze(clazz, initializer); + var instructions = initializer.instructions; + + for (int i = 1; i < instructions.size(); i++) { + var insn = instructions.get(i); + var prevInsn = insn.getPrevious(); + + /* + * We want instructions in the form of + * prevInsn: INVOKESTATIC ${clazz}.* (*)L*; || INVOKESPECIAL ${clazz}. (*)V + * insn: PUTSTATIC ${clazz}.* : L*; + */ + if (!isClassPutStatic(clazz, insn)) { + continue; // Ensure the current instruction is a PUTSTATIC for one of this class' fields + } + + var putStatic = (FieldInsnNode) insn; + if (!(prevInsn instanceof MethodInsnNode invokeInsn) || (!invokeInsn.owner.equals(clazz)) && !enumFields.contains(putStatic.name + ":" + putStatic.desc)) { + continue; // Ensure the previous instruction is an invocation of one of this class' methods, or an enum field, which may have an anonymous class + } + + if (invokeInsn.getOpcode() != INVOKESTATIC && !isInit(invokeInsn)) { + continue; // Ensure the invocation is either an INVOKESTATIC or a constructor invocation + } + + // Search for a name within the frame for the invocation instruction + String name = searchStringCstInStack(instructions, invokeInsn, frames); + + FieldEntry fieldEntry = fieldFromInsn(putStatic); + if (name == null) { + // If we couldn't find a name, try to link this field to one from another class instead + FieldInsnNode otherFieldInsn = searchFieldReferenceInStack(instructions, invokeInsn, frames, clazz); + + if (otherFieldInsn != null) { + this.linkedFields.put(fieldEntry, fieldFromInsn(otherFieldInsn)); + } + + continue; // Done with the current putStatic + } + + var fieldName = stringToUpperSnakeCase(processIdentifierPath(name)); + + if (fieldName == null || fieldName.isEmpty()) { + continue; + } + + if (!duplicatedNames.contains(fieldName) && usedNames.add(fieldName)) { + fieldNames.put(fieldEntry, fieldName); + } else { + duplicatedNames.add(fieldName); + Logger.warn("Duplicate field name \"{}\" (\"{}\") for field {}", fieldName, name, fieldEntry); + } + } + } + } +} diff --git a/src/main/java/org/quiltmc/enigma_plugin/index/enum_fields/FieldNameFinder.java b/src/main/java/org/quiltmc/enigma_plugin/index/enum_fields/FieldNameFinder.java deleted file mode 100644 index 1e0a8f6..0000000 --- a/src/main/java/org/quiltmc/enigma_plugin/index/enum_fields/FieldNameFinder.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright 2016, 2017, 2018, 2019 FabricMC - * Copyright 2022 QuiltMC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.quiltmc.enigma_plugin.index.enum_fields; - -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.FieldInsnNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.LdcInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.quiltmc.enigma.api.translation.representation.TypeDescriptor; -import org.quiltmc.enigma.api.translation.representation.entry.ClassEntry; -import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.analysis.Analyzer; -import org.objectweb.asm.tree.analysis.Frame; -import org.objectweb.asm.tree.analysis.SourceInterpreter; -import org.objectweb.asm.tree.analysis.SourceValue; -import org.tinylog.Logger; - -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; - -public class FieldNameFinder implements Opcodes { - private static boolean isClassPutStatic(String owner, AbstractInsnNode insn) { - return insn.getOpcode() == PUTSTATIC && ((FieldInsnNode) insn).owner.equals(owner); - } - - private static boolean isInit(AbstractInsnNode insn) { - return insn.getOpcode() == INVOKESPECIAL && ((MethodInsnNode) insn).name.equals(""); - } - - private static boolean isCharacterUsable(char c) { - return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_'; - } - - private static FieldEntry followFieldLink(FieldEntry field, Map linkedFields, Map names) { - if (names.containsKey(field)) { - return field; - } else if (linkedFields.containsKey(field)) { - return followFieldLink(linkedFields.get(field), linkedFields, names); - } - - return null; - } - - public Map findNames(EnumFieldsIndex enumIndex) throws Exception { - Analyzer analyzer = new Analyzer<>(new SourceInterpreter()); - Map fieldNames = new HashMap<>(); - Map> usedFieldNames = new HashMap<>(); - Map> duplicatedFieldNames = new HashMap<>(); - Map linkedFields = new HashMap<>(); - - for (Map.Entry> entry : enumIndex.getStaticInitializers().entrySet()) { - String owner = entry.getKey(); - Set enumFields = enumIndex.getEnumFields().getOrDefault(owner, Collections.emptySet()); - - for (MethodNode staticInitializer : entry.getValue()) { - Frame[] frames = analyzer.analyze(owner, staticInitializer); - InsnList instructions = staticInitializer.instructions; - - for (int i = 1; i < instructions.size(); i++) { - AbstractInsnNode insn1 = instructions.get(i - 1); - AbstractInsnNode insn2 = instructions.get(i); - - if (!isClassPutStatic(owner, insn2)) { - continue; - } - - FieldInsnNode fInsn2 = (FieldInsnNode) insn2; - if (!(insn1 instanceof MethodInsnNode mInsn1 && mInsn1.owner.equals(owner) || enumFields.contains(fInsn2.name + ":" + fInsn2.desc)) - || !(insn1.getOpcode() == INVOKESTATIC || isInit(insn1))) { - continue; - } - - // Search for a name within the frame - Frame frame = frames[i - 1]; - String name = null; - for (int j = 0; j < frame.getStackSize() && name == null; j++) { - SourceValue value = frame.getStack(j); - for (AbstractInsnNode insn : value.insns) { - if (insn instanceof LdcInsnNode ldcInsn && ldcInsn.cst instanceof String cst && !cst.isBlank()) { - name = cst; - break; - } - } - } - - if (name == null) { - // Try to link this field to another one - FieldInsnNode usedFieldInsn = null; - for (int j = 0; j < frame.getStackSize() && usedFieldInsn == null; j++) { - SourceValue value = frame.getStack(j); - AbstractInsnNode stackInsn = null; - for (AbstractInsnNode insn : value.insns) { - stackInsn = insn; - if (insn instanceof FieldInsnNode fInsn && fInsn.getOpcode() == GETSTATIC && !owner.equals(fInsn.owner)) { - usedFieldInsn = fInsn; - break; - } - } - - /* Search between the last stack instruction and the INVOKESPECIAL, useful for parameters passed to a constructor - NEW com/example/Clazz // Last instruction in the stack - DUP - GETSTATIC com/example/Test.FIELD : I // Instruction we are looking for - INVOKESPECIAL com/example/Clazz. (I)V - INVOKESPECIAL com/example/Test.foo (Lcom/example/Clazz;)Lcom/example/Test; // End INVOKESPECIAL - */ - if (usedFieldInsn == null && stackInsn != null) { - while (stackInsn.getNext() != null && stackInsn.getNext() != insn1) { - stackInsn = stackInsn.getNext(); - if (stackInsn instanceof FieldInsnNode fInsn && fInsn.getOpcode() == GETSTATIC && !owner.equals(fInsn.owner)) { - usedFieldInsn = fInsn; - break; - } - } - } - } - - if (usedFieldInsn != null) { - linkedFields.put(fieldFromInsn(fInsn2), fieldFromInsn(usedFieldInsn)); - } - - continue; - } - - // Remove identifier namespace - if (name.contains(":")) { - name = name.substring(name.lastIndexOf(":") + 1); - } - - // Process a path - if (name.contains("/")) { - int separator = name.indexOf("/"); - String first = name.substring(0, separator); - String last; - - if (name.contains(".") && name.indexOf(".") > separator) { - last = name.substring(separator + 1, name.indexOf(".")); - } else { - last = name.substring(separator + 1); - } - - if (first.endsWith("s")) { - first = first.substring(0, first.length() - 1); - } - - name = last + "_" + first; - } - - // Check if the name is usable, replace invalid characters, convert camel case to snake case - StringBuilder usableName = new StringBuilder(); - boolean hasText = false; - for (int j = 0; j < name.length(); j++) { - char c = name.charAt(j); - - if (isCharacterUsable(c)) { - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { - hasText = true; - } - - if (j > 0 && Character.isUpperCase(c) && Character.isLowerCase(usableName.charAt(usableName.length() - 1))) { - usableName.append('_'); - usableName.append(Character.toLowerCase(c)); - } else { - usableName.append(c); - } - } else { - usableName.append('_'); - } - } - - if (!hasText || usableName.isEmpty()) { - continue; - } - - String fieldName = usableName.toString().toUpperCase(Locale.ROOT); - - Set usedNames = usedFieldNames.computeIfAbsent(owner, k -> new HashSet<>()); - Set duplicatedNames = duplicatedFieldNames.computeIfAbsent(owner, k -> new HashSet<>()); - - if (!duplicatedNames.contains(fieldName)) { - if (!usedNames.add(fieldName)) { - Logger.warn("Duplicate key: " + fieldName + " (" + name + ") in " + owner); - duplicatedNames.add(fieldName); - usedNames.remove(fieldName); - } - } - - if (usedNames.contains(fieldName)) { - fieldNames.put(fieldFromInsn(fInsn2), fieldName); - } - } - } - } - - // Insert linked names - for (FieldEntry linked : linkedFields.keySet()) { - FieldEntry target = followFieldLink(linked, linkedFields, fieldNames); - String name = fieldNames.get(target); - - Set usedNames = usedFieldNames.computeIfAbsent(linked.getParent().getFullName(), s -> new HashSet<>()); - if (usedNames.add(name)) { - fieldNames.put(linked, name); - } - } - - return fieldNames; - } - - private static FieldEntry fieldFromInsn(FieldInsnNode insn) { - return new FieldEntry(new ClassEntry(insn.owner), insn.name, new TypeDescriptor(insn.desc)); - } -} diff --git a/src/main/java/org/quiltmc/enigma_plugin/proposal/EnumFieldNameProposer.java b/src/main/java/org/quiltmc/enigma_plugin/proposal/ConstantFieldNameProposer.java similarity index 72% rename from src/main/java/org/quiltmc/enigma_plugin/proposal/EnumFieldNameProposer.java rename to src/main/java/org/quiltmc/enigma_plugin/proposal/ConstantFieldNameProposer.java index 26ce4c2..57c75f8 100644 --- a/src/main/java/org/quiltmc/enigma_plugin/proposal/EnumFieldNameProposer.java +++ b/src/main/java/org/quiltmc/enigma_plugin/proposal/ConstantFieldNameProposer.java @@ -21,23 +21,23 @@ import org.quiltmc.enigma.api.translation.representation.entry.Entry; import org.quiltmc.enigma.api.translation.representation.entry.FieldEntry; import org.quiltmc.enigma_plugin.index.JarIndexer; -import org.quiltmc.enigma_plugin.index.enum_fields.EnumFieldsIndex; +import org.quiltmc.enigma_plugin.index.constant_fields.ConstantFieldIndex; import java.util.Map; -public class EnumFieldNameProposer extends NameProposer { - public static final String ID = "enum_fields"; - private final EnumFieldsIndex enumIndex; +public class ConstantFieldNameProposer extends NameProposer { + public static final String ID = "constant_fields"; + private final ConstantFieldIndex fieldIndex; - public EnumFieldNameProposer(JarIndexer index) { + public ConstantFieldNameProposer(JarIndexer index) { super(ID); - this.enumIndex = index.getEnumFieldsIndex(); + this.fieldIndex = index.getConstantFieldIndex(); } @Override public void insertProposedNames(JarIndex index, Map, EntryMapping> mappings) { - for (FieldEntry field : this.enumIndex.getFields()) { - String name = this.enumIndex.getName(field); + for (FieldEntry field : this.fieldIndex.getFields()) { + String name = this.fieldIndex.getName(field); this.insertProposal(mappings, field, name); } } diff --git a/src/main/java/org/quiltmc/enigma_plugin/proposal/NameProposerService.java b/src/main/java/org/quiltmc/enigma_plugin/proposal/NameProposerService.java index 06d3259..9a12149 100644 --- a/src/main/java/org/quiltmc/enigma_plugin/proposal/NameProposerService.java +++ b/src/main/java/org/quiltmc/enigma_plugin/proposal/NameProposerService.java @@ -38,7 +38,7 @@ public class NameProposerService implements NameProposalService { public NameProposerService(JarIndexer indexer, EnigmaServiceContext context) { this.addIfEnabled(context, indexer, Arguments.DISABLE_RECORDS, RecordComponentNameProposer::new); - this.addIfEnabled(context, indexer, Arguments.DISABLE_ENUM_FIELDS, EnumFieldNameProposer::new); + this.addIfEnabled(context, indexer, Arguments.DISABLE_CONSTANT_FIELDS, ConstantFieldNameProposer::new); this.addIfEnabled(context, Arguments.DISABLE_EQUALS, EqualsNameProposer::new); this.addIfEnabled(context, indexer, Arguments.DISABLE_LOGGER, LoggerNameProposer::new); this.addIfEnabled(context, indexer, Arguments.DISABLE_CODECS, CodecNameProposer::new); diff --git a/testProject/profile.json b/testProject/profile.json index 97f0901..b1e47a8 100644 --- a/testProject/profile.json +++ b/testProject/profile.json @@ -7,7 +7,7 @@ { "id": "quiltmc:name_proposal", "args": { - "disable_enum_fields": "false", + "disable_constant_fields": "false", "disable_records": "false", "disable_codecs": "false", "disable_map_non_hashed": "true" @@ -18,7 +18,7 @@ { "id": "quiltmc:jar_index", "args": { - "disable_enum_fields": "false", + "disable_constant_fields": "false", "disable_records": "false", "disable_codecs": "false" } diff --git a/testProject/src/main/java/com/example/field_names/ClassTest.java b/testProject/src/main/java/com/example/field_names/ClassTest.java index 3f113c1..189c4e1 100644 --- a/testProject/src/main/java/com/example/field_names/ClassTest.java +++ b/testProject/src/main/java/com/example/field_names/ClassTest.java @@ -3,11 +3,19 @@ public class ClassTest { public static final Something FOO = create("foo"); public static final Something BAR = create("foo/bar"); + public static final Something BAZ = create(create(create("foo/baz"))); public static final Something LOREM_IPSUM = create("baz/lorem_ipsum"); public static final Something AN_ID = create("example:an_id"); public static final Something ANOTHER_ID = create("example:foo/another_id"); + public static final Something ONE = create(new Something("One")); + public static final Something TWO = create(new Something(new Something("Two"))); + public static final Something THREE = create(new Something(new Something(new Something("Three")))); private static Something create(String value) { return new Something(value); } + + private static Something create(Something value) { + return new Something(value); + } } diff --git a/testProject/src/main/java/com/example/field_names/Enum2Test.java b/testProject/src/main/java/com/example/field_names/Enum2Test.java new file mode 100644 index 0000000..0dc6846 --- /dev/null +++ b/testProject/src/main/java/com/example/field_names/Enum2Test.java @@ -0,0 +1,35 @@ +package com.example.field_names; + +import java.util.Random; + +public enum Enum2Test { + CONSTANT, + INT { + @Override + public double randomValue(Random random) { + return random.nextInt(); + } + }, + DOUBLE { + @Override + public double randomValue(Random random) { + return random.nextDouble(); + } + }, + EXPONENTIAL { + @Override + public double randomValue(Random random) { + return random.nextExponential(); + } + }, + GAUSSIAN { + @Override + public double randomValue(Random random) { + return random.nextGaussian(); + } + }; + + public double randomValue(Random random) { + return 0; + } +}