fabric-loom/src/main/java/net/fabricmc/loom/util/accesswidener/AccessWidener.java
2020-04-06 22:52:49 +01:00

472 lines
14 KiB
Java

/*
* 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<String, Access> classAccess = new HashMap<>();
public Map<EntryTriple, Access> methodAccess = new HashMap<>();
public Map<EntryTriple, Access> fieldAccess = new HashMap<>();
private Set<String> 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<String> 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 (<access>\tclass\t<className>) 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 (<access>\tfield\t<className>\t<fieldName>\t<fieldDesc>) 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 (<access>\tmethod\t<className>\t<methodName>\t<methodDesc>) 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<String> 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<String, Access> 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<EntryTriple, Access> entry : methodAccess.entrySet()) {
writeEntry(writer, "method", entry.getKey(), entry.getValue());
}
for (Map.Entry<EntryTriple, Access> 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<String> getAccesses(Access access) {
List<String> 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<EntryTriple, Access> 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<EntryTriple, Access> 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<String, Access> 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<EntryTriple, Access> entry : other.methodAccess.entrySet()) {
addOrMerge(methodAccess, entry.getKey(), entry.getValue());
}
for (Map.Entry<EntryTriple, Access> 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<String> 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("<init>")) {
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);
}
}