/* * This file is part of fabric-loom, licensed under the MIT License (MIT). * * Copyright (c) 2016, 2017, 2018 FabricMC * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package net.fabricmc.loom.util.accesswidener; import java.io.BufferedReader; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.IntUnaryOperator; import org.objectweb.asm.Opcodes; import net.fabricmc.mappings.EntryTriple; public class AccessWidener { public String namespace; public Map classAccess = new HashMap<>(); public Map methodAccess = new HashMap<>(); public Map fieldAccess = new HashMap<>(); private Set classes = new LinkedHashSet<>(); public void read(BufferedReader reader) throws IOException { String[] header = reader.readLine().split("\\s+"); if (header.length != 3 || !header[0].equals("accessWidener")) { throw new UnsupportedOperationException("Invalid access access widener header"); } if (!header[1].equals("v1")) { throw new RuntimeException(String.format("Unsupported access widener format (%s)", header[1])); } if (namespace != null) { if (!namespace.equals(header[2])) { throw new RuntimeException(String.format("Namespace mismatch, expected %s got %s", namespace, header[2])); } } namespace = header[2]; String line; Set targets = new LinkedHashSet<>(); while ((line = reader.readLine()) != null) { //Comment handling int commentPos = line.indexOf('#'); if (commentPos >= 0) { line = line.substring(0, commentPos).trim(); } if (line.isEmpty()) continue; String[] split = line.split("\\s+"); if (split.length != 3 && split.length != 5) { throw new RuntimeException(String.format("Invalid line (%s)", line)); } String access = split[0]; targets.add(split[2].replaceAll("/", ".")); switch (split[1]) { case "class": if (split.length != 3) { throw new RuntimeException(String.format("Expected (\tclass\t) got (%s)", line)); } classAccess.put(split[2], applyAccess(access, classAccess.getOrDefault(split[2], ClassAccess.DEFAULT), null)); break; case "field": if (split.length != 5) { throw new RuntimeException(String.format("Expected (\tfield\t\t\t) got (%s)", line)); } addOrMerge(fieldAccess, new EntryTriple(split[2], split[3], split[4]), access, FieldAccess.DEFAULT); break; case "method": if (split.length != 5) { throw new RuntimeException(String.format("Expected (\tmethod\t\t\t) got (%s)", line)); } addOrMerge(methodAccess, new EntryTriple(split[2], split[3], split[4]), access, MethodAccess.DEFAULT); break; default: throw new UnsupportedOperationException("Unsupported type " + split[1]); } } Set parentClasses = new LinkedHashSet<>(); //Also transform all parent classes for (String clazz : targets) { while (clazz.contains("$")) { clazz = clazz.substring(0, clazz.lastIndexOf("$")); parentClasses.add(clazz); } } classes.addAll(targets); classes.addAll(parentClasses); } //Could possibly be cleaner but should do its job for now public void write(StringWriter writer) { writer.write("accessWidener\tv1\t"); writer.write(namespace); writer.write("\n"); for (Map.Entry entry : classAccess.entrySet()) { for (String s : getAccesses(entry.getValue())) { writer.write(s); writer.write("\tclass\t"); writer.write(entry.getKey()); writer.write("\n"); } } for (Map.Entry entry : methodAccess.entrySet()) { writeEntry(writer, "method", entry.getKey(), entry.getValue()); } for (Map.Entry entry : fieldAccess.entrySet()) { writeEntry(writer, "field", entry.getKey(), entry.getValue()); } } private void writeEntry(StringWriter writer, String type, EntryTriple entryTriple, Access access) { for (String s : getAccesses(access)) { writer.write(s); writer.write("\t"); writer.write(type); writer.write("\t"); writer.write(entryTriple.getOwner()); writer.write("\t"); writer.write(entryTriple.getName()); writer.write("\t"); writer.write(entryTriple.getDesc()); writer.write("\n"); } } private List getAccesses(Access access) { List accesses = new ArrayList<>(); if (access == ClassAccess.ACCESSIBLE || access == MethodAccess.ACCESSIBLE || access == FieldAccess.ACCESSIBLE || access == MethodAccess.ACCESSIBLE_EXTENDABLE || access == ClassAccess.ACCESSIBLE_EXTENDABLE || access == FieldAccess.ACCESSIBLE_MUTABLE) { accesses.add("accessible"); } if (access == ClassAccess.EXTENDABLE || access == MethodAccess.EXTENDABLE || access == MethodAccess.ACCESSIBLE_EXTENDABLE || access == ClassAccess.ACCESSIBLE_EXTENDABLE) { accesses.add("extendable"); } if (access == FieldAccess.MUTABLE || access == FieldAccess.ACCESSIBLE_MUTABLE) { accesses.add("mutable"); } return accesses; } void addOrMerge(Map map, EntryTriple entry, Access access) { if (entry == null || access == null) { throw new RuntimeException("Input entry or access is null"); } Access merged = null; if (access instanceof ClassAccess) { merged = ClassAccess.DEFAULT; } else if (access instanceof MethodAccess) { merged = MethodAccess.DEFAULT; } else if (access instanceof FieldAccess) { merged = FieldAccess.DEFAULT; } merged = mergeAccess(merged, access); map.put(entry, merged); } void addOrMerge(Map map, EntryTriple entry, String access, Access defaultAccess) { if (entry == null || access == null) { throw new RuntimeException("Input entry or access is null"); } map.put(entry, applyAccess(access, map.getOrDefault(entry, defaultAccess), entry)); } public void merge(AccessWidener other) { if (namespace == null) { namespace = other.namespace; } else if (!namespace.equals(other.namespace)) { throw new RuntimeException("Namespace mismatch"); } for (Map.Entry entry : other.classAccess.entrySet()) { if (classAccess.containsKey(entry.getKey())) { classAccess.replace(entry.getKey(), mergeAccess(classAccess.get(entry.getKey()), entry.getValue())); } else { classAccess.put(entry.getKey(), entry.getValue()); } } for (Map.Entry entry : other.methodAccess.entrySet()) { addOrMerge(methodAccess, entry.getKey(), entry.getValue()); } for (Map.Entry entry : other.fieldAccess.entrySet()) { addOrMerge(fieldAccess, entry.getKey(), entry.getValue()); } } private Access applyAccess(String input, Access access, EntryTriple entryTriple) { switch (input.toLowerCase(Locale.ROOT)) { case "accessible": makeClassAccessible(entryTriple); return access.makeAccessible(); case "extendable": makeClassExtendable(entryTriple); return access.makeExtendable(); case "mutable": return access.makeMutable(); default: throw new UnsupportedOperationException("Unknown access type:" + input); } } private void makeClassAccessible(EntryTriple entryTriple) { if (entryTriple == null) return; classAccess.put(entryTriple.getOwner(), applyAccess("accessible", classAccess.getOrDefault(entryTriple.getOwner(), ClassAccess.DEFAULT), null)); } private void makeClassExtendable(EntryTriple entryTriple) { if (entryTriple == null) return; classAccess.put(entryTriple.getOwner(), applyAccess("extendable", classAccess.getOrDefault(entryTriple.getOwner(), ClassAccess.DEFAULT), null)); } private static Access mergeAccess(Access a, Access b) { Access access = a; if (b == ClassAccess.ACCESSIBLE || b == MethodAccess.ACCESSIBLE || b == FieldAccess.ACCESSIBLE || b == MethodAccess.ACCESSIBLE_EXTENDABLE || b == ClassAccess.ACCESSIBLE_EXTENDABLE || b == FieldAccess.ACCESSIBLE_MUTABLE) { access = access.makeAccessible(); } if (b == ClassAccess.EXTENDABLE || b == MethodAccess.EXTENDABLE || b == MethodAccess.ACCESSIBLE_EXTENDABLE || b == ClassAccess.ACCESSIBLE_EXTENDABLE) { access = access.makeExtendable(); } if (b == FieldAccess.MUTABLE || b == FieldAccess.ACCESSIBLE_MUTABLE) { access = access.makeMutable(); } return access; } public Access getClassAccess(String className) { return classAccess.getOrDefault(className, ClassAccess.DEFAULT); } public Access getFieldAccess(EntryTriple entryTriple) { return fieldAccess.getOrDefault(entryTriple, FieldAccess.DEFAULT); } public Access getMethodAccess(EntryTriple entryTriple) { return methodAccess.getOrDefault(entryTriple, MethodAccess.DEFAULT); } public Set getTargets() { return classes; } private static int makePublic(int i) { return (i & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) | Opcodes.ACC_PUBLIC; } private static int makeProtected(int i) { if ((i & Opcodes.ACC_PUBLIC) != 0) { //Return i if public return i; } return (i & ~(Opcodes.ACC_PRIVATE)) | Opcodes.ACC_PROTECTED; } private static int makeFinalIfPrivate(int access, String name, int ownerAccess) { // Dont make constructors final if (name.equals("")) { return access; } // Skip interface and static methods if ((ownerAccess & (Opcodes.ACC_INTERFACE | Opcodes.ACC_STATIC)) != 0) { return access; } if ((access & Opcodes.ACC_PRIVATE) != 0) { return access | Opcodes.ACC_FINAL; } return access; } private static int removeFinal(int i) { return i & ~Opcodes.ACC_FINAL; } public interface Access extends AccessOperator { Access makeAccessible(); Access makeExtendable(); Access makeMutable(); } public enum ClassAccess implements Access { DEFAULT((access, name, ownerAccess) -> access), ACCESSIBLE((access, name, ownerAccess) -> makePublic(access)), EXTENDABLE((access, name, ownerAccess) -> makePublic(removeFinal(access))), ACCESSIBLE_EXTENDABLE((access, name, ownerAccess) -> makePublic(removeFinal(access))); private final AccessOperator operator; ClassAccess(AccessOperator operator) { this.operator = operator; } @Override public Access makeAccessible() { if (this == EXTENDABLE || this == ACCESSIBLE_EXTENDABLE) { return ACCESSIBLE_EXTENDABLE; } return ACCESSIBLE; } @Override public Access makeExtendable() { if (this == ACCESSIBLE || this == ACCESSIBLE_EXTENDABLE) { return ACCESSIBLE_EXTENDABLE; } return EXTENDABLE; } @Override public Access makeMutable() { throw new UnsupportedOperationException("Classes cannot be made mutable"); } @Override public int apply(int access, String targetName, int ownerAccess) { return operator.apply(access, targetName, ownerAccess); } } public enum MethodAccess implements Access { DEFAULT((access, name, ownerAccess) -> access), ACCESSIBLE((access, name, ownerAccess) -> makePublic(makeFinalIfPrivate(access, name, ownerAccess))), EXTENDABLE((access, name, ownerAccess) -> makeProtected(removeFinal(access))), ACCESSIBLE_EXTENDABLE((access, name, owner) -> makePublic(removeFinal(access))); private final AccessOperator operator; MethodAccess(AccessOperator operator) { this.operator = operator; } @Override public Access makeAccessible() { if (this == EXTENDABLE || this == ACCESSIBLE_EXTENDABLE) { return ACCESSIBLE_EXTENDABLE; } return ACCESSIBLE; } @Override public Access makeExtendable() { if (this == ACCESSIBLE || this == ACCESSIBLE_EXTENDABLE) { return ACCESSIBLE_EXTENDABLE; } return EXTENDABLE; } @Override public Access makeMutable() { throw new UnsupportedOperationException("Methods cannot be made mutable"); } @Override public int apply(int access, String targetName, int ownerAccess) { return operator.apply(access, targetName, ownerAccess); } } public enum FieldAccess implements Access { DEFAULT((access, name, ownerAccess) -> access), ACCESSIBLE((access, name, ownerAccess) -> makePublic(access)), MUTABLE((access, name, ownerAccess) -> removeFinal(access)), ACCESSIBLE_MUTABLE((access, name, ownerAccess) -> makePublic(removeFinal(access))); private final AccessOperator operator; FieldAccess(AccessOperator operator) { this.operator = operator; } @Override public Access makeAccessible() { if (this == MUTABLE || this == ACCESSIBLE_MUTABLE) { return ACCESSIBLE_MUTABLE; } return ACCESSIBLE; } @Override public Access makeExtendable() { throw new UnsupportedOperationException("Fields cannot be made extendable"); } @Override public Access makeMutable() { if (this == ACCESSIBLE || this == ACCESSIBLE_MUTABLE) { return ACCESSIBLE_MUTABLE; } return MUTABLE; } @Override public int apply(int access, String targetName, int ownerAccess) { return operator.apply(access, targetName, ownerAccess); } } @FunctionalInterface public interface AccessOperator { int apply(int access, String targetName, int ownerAccess); } }