Emit compile-time errors for mislabeled mixins that targets client side classes
This commit is contained in:
parent
4645c26227
commit
2660df6db3
|
|
@ -0,0 +1,114 @@
|
|||
package org.fury_phoenix.mixinAp.annotation;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
import net.fabricmc.api.Environment;
|
||||
import net.fabricmc.api.EnvType;
|
||||
|
||||
import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.util.asm.IAnnotationHandle;
|
||||
import org.spongepowered.tools.obfuscation.AnnotatedMixinsAccessor;
|
||||
import org.spongepowered.tools.obfuscation.interfaces.ITypeHandleProvider;
|
||||
import org.spongepowered.tools.obfuscation.mirror.TypeHandle;
|
||||
|
||||
import static java.util.AbstractMap.SimpleImmutableEntry;
|
||||
|
||||
public class ClientMixinValidator {
|
||||
|
||||
private final ITypeHandleProvider typeHandleProvider;
|
||||
|
||||
private final ProcessingEnvironment processingEnv;
|
||||
|
||||
public ClientMixinValidator(ProcessingEnvironment env) {
|
||||
typeHandleProvider = AnnotatedMixinsAccessor.getMixinAP(env);
|
||||
processingEnv = env;
|
||||
}
|
||||
// some sort of javac bug with method reference resolution for mixed staticness
|
||||
public boolean targetsClient(TypeElement annotatedMixinClass) {
|
||||
return targetsClient(
|
||||
ClientMixinValidator.getTargets(
|
||||
getAnnotationHandle(annotatedMixinClass, Mixin.class)
|
||||
)) && !getAnnotationHandle(annotatedMixinClass, ClientOnlyMixin.class).exists();
|
||||
}
|
||||
|
||||
private boolean targetsClient(List<?> classTargets) {
|
||||
return classTargets.stream()
|
||||
.anyMatch(this::targetsClient);
|
||||
}
|
||||
|
||||
private boolean targetsClient(Object classTarget) {
|
||||
return switch (classTarget) {
|
||||
case null -> throw new IllegalArgumentException("Can't be empty!");
|
||||
case TypeMirror tm ->
|
||||
EnvType.CLIENT == getEnvType(tm);
|
||||
// If you're using a dollar sign in class names you are insane
|
||||
case String s && (getEnvType(s) != null) ->
|
||||
EnvType.CLIENT == getEnvType(s);
|
||||
case String s -> warn(s);
|
||||
default ->
|
||||
throw new IllegalArgumentException("Unhandled type: " + classTarget.getClass() + "\n"
|
||||
+ "Stringified contents: " + classTarget.toString());
|
||||
};
|
||||
}
|
||||
|
||||
private EnvType getEnvType(Object o) {
|
||||
TypeHandle handle = getTypeHandle(o);
|
||||
if(handle.isImaginary())
|
||||
return null;
|
||||
Environment env = handle.getElement().getAnnotation(Environment.class);
|
||||
if(env == null)
|
||||
return null;
|
||||
return env.value();
|
||||
}
|
||||
|
||||
private boolean warn(String s) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, s + "can't be loaded, so it is skipped!");
|
||||
return false;
|
||||
}
|
||||
|
||||
public SimpleImmutableEntry<? extends CharSequence, ? extends CharSequence>
|
||||
getEntry(TypeElement annotatedMixinClass) {
|
||||
return new SimpleImmutableEntry<>(
|
||||
annotatedMixinClass.getQualifiedName(),
|
||||
ClientMixinValidator.getTargets(
|
||||
getAnnotationHandle(annotatedMixinClass, Mixin.class)
|
||||
).stream().filter(this::targetsClient)
|
||||
.map(Object::toString)
|
||||
.map(ClientMixinValidator::toSourceString)
|
||||
.collect(Collectors.joining(", "))
|
||||
);
|
||||
}
|
||||
|
||||
private TypeHandle getTypeHandle(Object annotatedClass) {
|
||||
return typeHandleProvider.getTypeHandle(annotatedClass);
|
||||
}
|
||||
|
||||
private IAnnotationHandle getAnnotationHandle(Object annotatedClass, Class<? extends Annotation> annotation) {
|
||||
return getTypeHandle(annotatedClass).getAnnotation(annotation);
|
||||
}
|
||||
|
||||
public static String toSourceString(String bytecodeName) {
|
||||
return bytecodeName.replaceAll("\\/", ".");
|
||||
}
|
||||
|
||||
private static List<Object> getTargets(IAnnotationHandle mixinAnnotation) {
|
||||
Collection<? extends TypeMirror> clzss = mixinAnnotation.getList("value");
|
||||
Collection<? extends String> imaginary = mixinAnnotation.getList("targets");
|
||||
List<Object> targets =
|
||||
Stream.of(clzss, imaginary)
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
return targets;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,16 @@
|
|||
package org.fury_phoenix.mixinAp.annotation;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.common.base.Throwables;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.List;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.annotation.processing.AbstractProcessor;
|
||||
import javax.annotation.processing.Processor;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import javax.annotation.processing.SupportedOptions;
|
||||
|
|
@ -24,7 +19,6 @@ import javax.lang.model.SourceVersion;
|
|||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.StandardLocation;
|
||||
|
||||
import org.fury_phoenix.mixinAp.config.MixinConfig;
|
||||
|
||||
|
|
@ -40,16 +34,8 @@ public class MixinProcessor extends AbstractProcessor {
|
|||
"ClientOnlyMixin", "client"
|
||||
);
|
||||
|
||||
private String rootProjectName;
|
||||
|
||||
private final Map<String, List<String>> mixinConfigList = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public void init(ProcessingEnvironment processingEnv) {
|
||||
super.init(processingEnv);
|
||||
rootProjectName = processingEnv.getOptions().get("rootProject.name");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
|
||||
if(roundEnv.processingOver()){
|
||||
|
|
@ -57,12 +43,10 @@ public class MixinProcessor extends AbstractProcessor {
|
|||
// create record for serialization, compute package name
|
||||
String packageName = mixinConfigList.get("mixins").get(0).split("(?<=mixin)")[0];
|
||||
finalizeMixinConfig();
|
||||
generateMixinConfig(
|
||||
new MixinConfig(packageName,
|
||||
mixinConfigList.get("mixins"),
|
||||
mixinConfigList.get("client")
|
||||
)
|
||||
);
|
||||
new MixinConfig(packageName,
|
||||
mixinConfigList.get("mixins"),
|
||||
mixinConfigList.get("client")
|
||||
).generateMixinConfig(processingEnv);
|
||||
} else {
|
||||
processMixins(annotations, roundEnv);
|
||||
}
|
||||
|
|
@ -73,8 +57,14 @@ public class MixinProcessor extends AbstractProcessor {
|
|||
for (TypeElement annotation : annotations) {
|
||||
Set<? extends Element> annotatedMixins = roundEnv.getElementsAnnotatedWith(annotation);
|
||||
|
||||
List<String> mixins = annotatedMixins.stream()
|
||||
.filter(TypeElement.class::isInstance)
|
||||
Stream<TypeElement> mixinStream =
|
||||
annotatedMixins.stream()
|
||||
.map(TypeElement.class::cast);
|
||||
|
||||
validateCommonMixins(annotation, mixinStream);
|
||||
|
||||
List<String> mixins =
|
||||
annotatedMixins.stream()
|
||||
.map(TypeElement.class::cast)
|
||||
.map(TypeElement::toString)
|
||||
.collect(Collectors.toList());
|
||||
|
|
@ -84,34 +74,23 @@ public class MixinProcessor extends AbstractProcessor {
|
|||
}
|
||||
|
||||
private void filterMixinSets() {
|
||||
// set difference of mixins and client
|
||||
List<String> commonSet = mixinConfigList.get("mixins");
|
||||
commonSet.removeAll(mixinConfigList.get("client"));
|
||||
}
|
||||
|
||||
private String computeMixinConfigPath() {
|
||||
var projectName = processingEnv.getOptions().get("project.name");
|
||||
return "resources/" +
|
||||
rootProjectName +
|
||||
(projectName != null ? "-" + projectName : "") +
|
||||
".mixins.json";
|
||||
private void validateCommonMixins(TypeElement annotation, Stream<TypeElement> mixins) {
|
||||
if(!annotation.getSimpleName().toString().equals("Mixin"))
|
||||
return;
|
||||
ClientMixinValidator validator = new ClientMixinValidator(processingEnv);
|
||||
mixins.parallel()
|
||||
.filter(validator::targetsClient)
|
||||
.map(validator::getEntry)
|
||||
.forEach(this::logClientClassTarget);
|
||||
}
|
||||
|
||||
private void generateMixinConfig(Object config) {
|
||||
try (Writer mixinConfigWriter = processingEnv.getFiler()
|
||||
.createResource(StandardLocation.SOURCE_OUTPUT, "", computeMixinConfigPath())
|
||||
.openWriter()) {
|
||||
String mixinConfig = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
.toJson(config);
|
||||
|
||||
mixinConfigWriter.write(mixinConfig);
|
||||
mixinConfigWriter.write("\n");
|
||||
} catch (IOException e) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Fatal error:" +
|
||||
Throwables.getStackTraceAsString(e));
|
||||
}
|
||||
private void logClientClassTarget(Map.Entry<? extends CharSequence, ? extends CharSequence> mixin) {
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
|
||||
"Mixin " + mixin.getKey() + " targets client-side classes: " + mixin.getValue());
|
||||
}
|
||||
|
||||
private void finalizeMixinConfig() {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,18 @@
|
|||
package org.fury_phoenix.mixinAp.config;
|
||||
|
||||
import java.util.List;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.StandardLocation;
|
||||
|
||||
public record MixinConfig(
|
||||
boolean required,
|
||||
String minVersion,
|
||||
|
|
@ -26,4 +36,34 @@ public record MixinConfig(
|
|||
public record OverwriteOptions(boolean conformVisibility) {
|
||||
public static final OverwriteOptions DEFAULT = new OverwriteOptions(true);
|
||||
}
|
||||
|
||||
public void generateMixinConfig(ProcessingEnvironment env) {
|
||||
try (
|
||||
Writer mixinConfigWriter = env.getFiler()
|
||||
.createResource(StandardLocation.SOURCE_OUTPUT, "",
|
||||
MixinConfig.computeMixinConfigPath(
|
||||
env.getOptions().get("rootProject.name"),
|
||||
Optional.ofNullable(env.getOptions().get("project.name"))
|
||||
)
|
||||
).openWriter()
|
||||
) {
|
||||
String mixinConfig = new GsonBuilder()
|
||||
.setPrettyPrinting()
|
||||
.create()
|
||||
.toJson(this);
|
||||
|
||||
mixinConfigWriter.write(mixinConfig);
|
||||
mixinConfigWriter.write("\n");
|
||||
} catch (IOException e) {
|
||||
env.getMessager().printMessage(Diagnostic.Kind.ERROR, "Fatal error:" +
|
||||
Throwables.getStackTraceAsString(e));
|
||||
}
|
||||
}
|
||||
|
||||
private static String computeMixinConfigPath(String rootProjectName, Optional<String> projectName) {
|
||||
return "resources/" +
|
||||
rootProjectName +
|
||||
projectName.orElse("") +
|
||||
".mixins.json";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package org.spongepowered.tools.obfuscation;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
|
||||
import org.spongepowered.tools.obfuscation.interfaces.ITypeHandleProvider;
|
||||
|
||||
public class AnnotatedMixinsAccessor {
|
||||
public static ITypeHandleProvider getMixinAP(ProcessingEnvironment processingEnv) {
|
||||
return AnnotatedMixins.getMixinsForEnvironment(processingEnv);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user