Access widener support (#182)

* Rough work on project based jars, skeleton for AccessEscalators?

* First working draft

* Minor changes

* Add support for mutable, better error checking when parsing file.
Code cleanup
Remap if needed when reading

* Fix inner classes and genSources

* Fix CME

* Caching, only regen jar when input changes

* Some work, untested

* Fix writing, fix checkstyle issues

* More fixes

* Move jars into a maven file structure, cleans up the file structure, and will benefit idea 2020
Add some basic validation to the AccessWidenerRemapper, will present any issues with the mappings when building (May need a way to disable?)
+ Some bugs fixes

* Fix issues with source jars in idea 2020, should be backwards compatible with 2019

* Move to lorenz-tiny

* Build fix + small cleanup

* Update to match the changes in loader

* More fixes

* Update to match loader changes.

* Improve error logging
dev/0.11
modmuss50 2020-04-06 15:28:53 +01:00 committed by GitHub
parent 75f08fc4c5
commit 0ae8535c40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 876 additions and 0 deletions

View File

@ -31,6 +31,7 @@ import java.util.List;
import org.gradle.api.Project; import org.gradle.api.Project;
import net.fabricmc.loom.util.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradleExtension;
public class JarProcessorManager { public class JarProcessorManager {
@ -49,6 +50,10 @@ public class JarProcessorManager {
private List<JarProcessor> setupProcessors() { private List<JarProcessor> setupProcessors() {
List<JarProcessor> jarProcessors = new ArrayList<>(); List<JarProcessor> jarProcessors = new ArrayList<>();
if (extension.accessWidener != null) {
jarProcessors.add(new AccessWidenerJarProcessor());
}
jarProcessors.forEach(jarProcessor -> jarProcessor.setup(project)); jarProcessors.forEach(jarProcessor -> jarProcessor.setup(project));
return Collections.unmodifiableList(jarProcessors); return Collections.unmodifiableList(jarProcessors);
} }

View File

@ -45,6 +45,7 @@ import net.fabricmc.loom.util.GradleSupport;
import net.fabricmc.loom.util.MixinRefmapHelper; import net.fabricmc.loom.util.MixinRefmapHelper;
import net.fabricmc.loom.util.NestedJars; import net.fabricmc.loom.util.NestedJars;
import net.fabricmc.loom.util.TinyRemapperMappingsHelper; import net.fabricmc.loom.util.TinyRemapperMappingsHelper;
import net.fabricmc.loom.util.accesswidener.AccessWidenerJarProcessor;
import net.fabricmc.tinyremapper.OutputConsumerPath; import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper; import net.fabricmc.tinyremapper.TinyRemapper;
import net.fabricmc.tinyremapper.TinyUtils; import net.fabricmc.tinyremapper.TinyUtils;
@ -128,6 +129,10 @@ public class RemapJarTask extends Jar {
} }
} }
if (extension.accessWidener != null) {
extension.getJarProcessorManager().getByType(AccessWidenerJarProcessor.class).remapAccessWidener(output);
}
/*try { /*try {
if (modJar.exists()) { if (modJar.exists()) {
Files.move(modJar, modJarUnmappedCopy); Files.move(modJar, modJarUnmappedCopy);

View File

@ -24,10 +24,13 @@
package net.fabricmc.loom.util; package net.fabricmc.loom.util;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -47,12 +50,15 @@ import org.gradle.api.artifacts.Configuration;
import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.ResolvedArtifact;
import org.zeroturnaround.zip.ZipUtil; import org.zeroturnaround.zip.ZipUtil;
import org.zeroturnaround.zip.commons.FileUtils; import org.zeroturnaround.zip.commons.FileUtils;
import org.zeroturnaround.zip.transform.ByteArrayZipEntryTransformer;
import org.zeroturnaround.zip.transform.StringZipEntryTransformer; import org.zeroturnaround.zip.transform.StringZipEntryTransformer;
import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry; import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry;
import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.providers.MappingsProvider; import net.fabricmc.loom.providers.MappingsProvider;
import net.fabricmc.loom.providers.MinecraftMappedProvider; import net.fabricmc.loom.providers.MinecraftMappedProvider;
import net.fabricmc.loom.util.accesswidener.AccessWidener;
import net.fabricmc.loom.util.accesswidener.AccessWidenerRemapper;
import net.fabricmc.tinyremapper.OutputConsumerPath; import net.fabricmc.tinyremapper.OutputConsumerPath;
import net.fabricmc.tinyremapper.TinyRemapper; import net.fabricmc.tinyremapper.TinyRemapper;
@ -71,6 +77,8 @@ public class ModProcessor {
handleNestedJars(input, project, config, artifact); handleNestedJars(input, project, config, artifact);
} }
remapaccessWidener(input, project);
//Always strip the nested jars //Always strip the nested jars
stripNestedJars(output); stripNestedJars(output);
} }
@ -140,6 +148,57 @@ public class ModProcessor {
}))}); }))});
} }
private static void remapaccessWidener(File input, Project project) throws IOException {
JarFile jarFile = new JarFile(input);
JarEntry modJsonEntry = jarFile.getJarEntry("fabric.mod.json");
if (modJsonEntry == null) {
return;
}
String accessWidenerPath;
try (InputStream inputStream = jarFile.getInputStream(modJsonEntry)) {
JsonObject json = GSON.fromJson(new InputStreamReader(inputStream), JsonObject.class);
if (!json.has("accessWidener")) {
return;
}
accessWidenerPath = json.get("accessWidener").getAsString();
}
if (accessWidenerPath == null) {
return;
}
ZipUtil.transformEntry(input, accessWidenerPath, new ByteArrayZipEntryTransformer() {
@Override
protected byte[] transform(ZipEntry zipEntry, byte[] input) throws IOException {
return remapaccessWidener(input, project);
}
});
}
private static byte[] remapaccessWidener(byte[] input, Project project) {
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(input)))) {
AccessWidener accessWidener = new AccessWidener();
accessWidener.read(bufferedReader);
AccessWidenerRemapper accessWidenerRemapper = new AccessWidenerRemapper(accessWidener, project.getExtensions().getByType(LoomGradleExtension.class).getMappingsProvider().getMappings(), "named");
AccessWidener remapped = accessWidenerRemapper.remap();
StringWriter writer = new StringWriter();
remapped.write(writer);
byte[] bytes = writer.toString().getBytes();
writer.close();
return bytes;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private static void remapJar(File input, File output, Project project, ResolvedArtifact artifact) throws IOException { private static void remapJar(File input, File output, Project project, ResolvedArtifact artifact) throws IOException {
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class); LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
String fromM = "intermediary"; String fromM = "intermediary";

View File

@ -0,0 +1,457 @@
/*
* 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 i) {
if ((i & Opcodes.ACC_PRIVATE) != 0) {
return i | Opcodes.ACC_FINAL;
}
return i;
}
private static int removeFinal(int i) {
return i & ~Opcodes.ACC_FINAL;
}
public interface Access extends IntUnaryOperator {
Access makeAccessible();
Access makeExtendable();
Access makeMutable();
}
public enum ClassAccess implements Access {
DEFAULT(i -> i),
ACCESSIBLE(i -> makePublic(i)),
EXTENDABLE(i -> makePublic(removeFinal(i))),
ACCESSIBLE_EXTENDABLE(i -> makePublic(removeFinal(i)));
private final IntUnaryOperator operator;
ClassAccess(IntUnaryOperator 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 applyAsInt(int operand) {
return operator.applyAsInt(operand);
}
}
public enum MethodAccess implements Access {
DEFAULT(i -> i),
ACCESSIBLE(i -> makePublic(makeFinalIfPrivate(i))),
EXTENDABLE(i -> makeProtected(removeFinal(i))),
ACCESSIBLE_EXTENDABLE(i -> makePublic(removeFinal(i)));
private final IntUnaryOperator operator;
MethodAccess(IntUnaryOperator 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 applyAsInt(int operand) {
return operator.applyAsInt(operand);
}
}
public enum FieldAccess implements Access {
DEFAULT(i -> i),
ACCESSIBLE(i -> makePublic(i)),
MUTABLE(i -> removeFinal(i)),
ACCESSIBLE_MUTABLE(i -> makePublic(removeFinal(i)));
private final IntUnaryOperator operator;
FieldAccess(IntUnaryOperator 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 applyAsInt(int operand) {
return operator.applyAsInt(operand);
}
}
}

View File

@ -0,0 +1,240 @@
/*
* 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.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Set;
import java.util.zip.ZipEntry;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.gradle.api.Project;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.zeroturnaround.zip.ZipUtil;
import org.zeroturnaround.zip.transform.ByteArrayZipEntryTransformer;
import org.zeroturnaround.zip.transform.ZipEntryTransformer;
import org.zeroturnaround.zip.transform.ZipEntryTransformerEntry;
import net.fabricmc.mappings.EntryTriple;
import net.fabricmc.loom.LoomGradleExtension;
import net.fabricmc.loom.util.Checksum;
import net.fabricmc.loom.processors.JarProcessor;
public class AccessWidenerJarProcessor implements JarProcessor {
private AccessWidener accessWidener = new AccessWidener();
private Project project;
private byte[] inputHash;
@Override
public void setup(Project project) {
this.project = project;
LoomGradleExtension loomGradleExtension = project.getExtensions().getByType(LoomGradleExtension.class);
if (!loomGradleExtension.accessWidener.exists()) {
throw new RuntimeException("Could not find access widener file @ " + loomGradleExtension.accessWidener.getAbsolutePath());
}
inputHash = Checksum.sha256(loomGradleExtension.accessWidener);
try (BufferedReader reader = new BufferedReader(new FileReader(loomGradleExtension.accessWidener))) {
accessWidener.read(reader);
} catch (IOException e) {
throw new RuntimeException("Failed to read project access widener file");
}
//Remap accessWidener if its not named, allows for AE's to be written in intermediary
if (!accessWidener.namespace.equals("named")) {
try {
AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, loomGradleExtension.getMappingsProvider().getMappings(), "named");
accessWidener = remapper.remap();
} catch (IOException e) {
throw new RuntimeException("Failed to remap access widener", e);
}
}
}
@Override
public void process(File file) {
project.getLogger().lifecycle("Processing file: " + file.getName());
ZipUtil.transformEntries(file, getTransformers(accessWidener.getTargets()));
ZipUtil.addEntry(file, "aw.sha256", inputHash);
}
private ZipEntryTransformerEntry[] getTransformers(Set<String> classes) {
return classes.stream()
.map(string -> new ZipEntryTransformerEntry(string.replaceAll("\\.", "/") + ".class", getTransformer(string)))
.toArray(ZipEntryTransformerEntry[]::new);
}
private ZipEntryTransformer getTransformer(String className) {
return new ByteArrayZipEntryTransformer() {
@Override
protected byte[] transform(ZipEntry zipEntry, byte[] input) {
ClassReader reader = new ClassReader(input);
ClassWriter writer = new ClassWriter(0);
project.getLogger().lifecycle("Applying access widener to " + className);
reader.accept(new AccessTransformer(writer), 0);
return writer.toByteArray();
}
};
}
//Called when remapping the mod
public void remapAccessWidener(Path modJarPath) throws IOException {
LoomGradleExtension extension = project.getExtensions().getByType(LoomGradleExtension.class);
AccessWidenerRemapper remapper = new AccessWidenerRemapper(accessWidener, extension.getMappingsProvider().getMappings(), "intermediary");
AccessWidener remapped = remapper.remap();
StringWriter writer = new StringWriter();
remapped.write(writer);
byte[] bytes = writer.toString().getBytes();
writer.close();
String path = getAccessWidenerPath(modJarPath);
if (path == null) {
return;
}
boolean replaced = ZipUtil.replaceEntry(modJarPath.toFile(), path, bytes);
if (!replaced) {
project.getLogger().warn("Failed to replace access widener file at " + path);
}
}
private String getAccessWidenerPath(Path modJarPath) {
byte[] modJsonBytes = ZipUtil.unpackEntry(modJarPath.toFile(), "fabric.mod.json");
if (modJsonBytes == null) {
return null;
}
JsonObject jsonObject = new Gson().fromJson(new String(modJsonBytes, StandardCharsets.UTF_8), JsonObject.class);
if (!jsonObject.has("accessWidener")) {
return null;
}
return jsonObject.get("accessWidener").getAsString();
}
@Override
public boolean isInvalid(File file) {
byte[] hash = ZipUtil.unpackEntry(file, "aw.sha256");
if (hash == null) {
return true;
}
return !Arrays.equals(inputHash, hash); //TODO how do we know if the current jar as the correct access applied? save the hash of the input?
}
private class AccessTransformer extends ClassVisitor {
private String className;
private AccessTransformer(ClassVisitor classVisitor) {
super(Opcodes.ASM7, classVisitor);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
className = name;
super.visit(
version,
accessWidener.getClassAccess(name).applyAsInt(access),
name,
signature,
superName,
interfaces
);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
super.visitInnerClass(
name,
outerName,
innerName,
accessWidener.getClassAccess(name).applyAsInt(access)
);
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
return super.visitField(
accessWidener.getFieldAccess(new EntryTriple(className, name, descriptor)).applyAsInt(access),
name,
descriptor,
signature,
value
);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
return new AccessWidenerMethodVisitor(super.visitMethod(
accessWidener.getMethodAccess(new EntryTriple(className, name, descriptor)).applyAsInt(access),
name,
descriptor,
signature,
exceptions
));
}
private class AccessWidenerMethodVisitor extends MethodVisitor {
AccessWidenerMethodVisitor(MethodVisitor methodVisitor) {
super(Opcodes.ASM7, methodVisitor);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
if (opcode == Opcodes.INVOKESPECIAL && owner.equals(className) && !name.equals("<init>")) {
AccessWidener.Access methodAccess = accessWidener.getMethodAccess(new EntryTriple(owner, name, descriptor));
if (methodAccess != AccessWidener.MethodAccess.DEFAULT) {
opcode = Opcodes.INVOKEVIRTUAL;
}
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.util.HashMap;
import java.util.Map;
import net.fabricmc.mapping.tree.ClassDef;
import net.fabricmc.mapping.tree.FieldDef;
import net.fabricmc.mapping.tree.MethodDef;
import net.fabricmc.mapping.tree.TinyTree;
import net.fabricmc.mappings.EntryTriple;
public class AccessWidenerRemapper {
private final AccessWidener input;
private final String from, to;
private Map<String, String> classNames = new HashMap<>();
private Map<EntryTriple, EntryTriple> fieldNames = new HashMap<>();
private Map<EntryTriple, EntryTriple> methodNames = new HashMap<>();
public AccessWidenerRemapper(AccessWidener input, TinyTree tinyTree, String to) {
this.input = input;
this.from = input.namespace;
this.to = to;
populateMappings(tinyTree);
}
private void populateMappings(TinyTree tinyTree) {
if (!tinyTree.getMetadata().getNamespaces().contains(from)) {
throw new UnsupportedOperationException("Unknown namespace: " + from);
}
if (!tinyTree.getMetadata().getNamespaces().contains(to)) {
throw new UnsupportedOperationException("Unknown namespace: " + to);
}
for (ClassDef classDef : tinyTree.getClasses()) {
classNames.put(classDef.getName(from), classDef.getName(to));
for (FieldDef fieldDef : classDef.getFields()) {
EntryTriple fromEntry = new EntryTriple(classDef.getName(from), fieldDef.getName(from), fieldDef.getDescriptor(from));
EntryTriple toEntry = new EntryTriple(classDef.getName(to), fieldDef.getName(to), fieldDef.getDescriptor(to));
fieldNames.put(fromEntry, toEntry);
}
for (MethodDef methodDef : classDef.getMethods()) {
EntryTriple fromEntry = new EntryTriple(classDef.getName(from), methodDef.getName(from), methodDef.getDescriptor(from));
EntryTriple toEntry = new EntryTriple(classDef.getName(to), methodDef.getName(to), methodDef.getDescriptor(to));
methodNames.put(fromEntry, toEntry);
}
}
}
public AccessWidener remap() {
//Dont remap if we dont need to
if (input.namespace.equals(to)) {
return input;
}
AccessWidener remapped = new AccessWidener();
remapped.namespace = to;
for (Map.Entry<String, AccessWidener.Access> entry : input.classAccess.entrySet()) {
remapped.classAccess.put(findMapping(classNames, entry.getKey()), entry.getValue());
}
for (Map.Entry<EntryTriple, AccessWidener.Access> entry : input.methodAccess.entrySet()) {
remapped.addOrMerge(remapped.methodAccess, findMapping(methodNames, entry.getKey()), entry.getValue());
}
for (Map.Entry<EntryTriple, AccessWidener.Access> entry : input.fieldAccess.entrySet()) {
remapped.addOrMerge(remapped.fieldAccess, findMapping(fieldNames, entry.getKey()), entry.getValue());
}
return remapped;
}
private static <K, V> V findMapping(Map<K, V> map, K key) {
V value = map.get(key);
if (value == null) {
throw new RuntimeException("Failed to find mapping for " + key.toString());
}
return value;
}
}