Skip to content

Commit

Permalink
Fix annotations not applying properly to packages.
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexIIL committed Mar 30, 2024
1 parent 8b7fb2e commit 1c74011
Show file tree
Hide file tree
Showing 18 changed files with 330 additions and 97 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package net.fabricmc.minecraft.test.server_only;

import org.quiltmc.loader.api.minecraft.DedicatedServerOnly;

@DedicatedServerOnly
public class TestMixinGuiHelper {

public static void help() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// @org.quiltmc.loader.api.minecraft.DedicatedServerOnly
//@org.quiltmc.loader.api.minecraft.DedicatedServerOnly
//@org.quiltmc.loader.api.Requires({"trinkets", "quilt_loader", "minecraft", "buildcraft"})
// Uncomment the above line to test out package based stripping.
package net.fabricmc.minecraft.test.server_only;
2 changes: 1 addition & 1 deletion src/main/java/org/quiltmc/loader/api/Requires.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

/** Applied to declare that the annotated element requires specific mods to exist.
* <p>
* When applied to mod code this will result in quilt-loader removing that element when running without the specified mods.
* When applied to mod code this will result in quilt-loader removing that element when running without all of the specified mods.
* <p>
* When the annotated element is removed, bytecode associated with the element will not be removed. For example, if a
* field is removed, its initializer code will not, and will cause an error on execution.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.net.URL;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Manifest;

Expand All @@ -40,6 +41,7 @@ public interface QuiltLauncher {
void setAllowedPrefixes(Path path, String... prefixes);
void setTransformCache(URL insideTransformCache);
void setHiddenClasses(Set<String> classes);
void setHiddenClasses(Map<String, String> classes);
void hideParentUrl(URL hidden);
void hideParentPath(Path obf);
void validateGameClassLoader(Object gameInstance);
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/org/quiltmc/loader/impl/launch/knot/Knot.java
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,11 @@ public void setHiddenClasses(Set<String> hiddenClasses) {
classLoader.getDelegate().setHiddenClasses(hiddenClasses);
}

@Override
public void setHiddenClasses(Map<String, String> hiddenClasses) {
classLoader.getDelegate().setHiddenClasses(hiddenClasses);
}

@Override
public void hideParentUrl(URL parent) {
classLoader.getDelegate().hideParentUrl(parent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@
import java.security.CodeSource;
import java.security.cert.Certificate;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -97,7 +99,7 @@ public Optional<ModContainer> getQuiltMod() {
private IMixinTransformer mixinTransformer;
private boolean transformInitialized = false;
private boolean transformFinishedLoading = false;
private Set<String> hiddenClasses = Collections.emptySet();
private Map<String, String> hiddenClasses = Collections.emptyMap();
private String transformCacheUrl;
private final Map<String, String[]> allowedPrefixes = new ConcurrentHashMap<>();
private final Set<String> parentSourcedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>());
Expand All @@ -106,8 +108,8 @@ public Optional<ModContainer> getQuiltMod() {
* in this loader. */
private final Set<String> parentHiddenUrls = Collections.newSetFromMap(new ConcurrentHashMap<>());

/** Map of package to whether we can load it in this environment. */
private final Map<String, Boolean> packageSideCache = new ConcurrentHashMap<>();
/** Map of package to the reason why it cannot be loaded. If the package can be loaded then the value is the empty string. */
private final Map<String, String> packageLoadDenyCache = new ConcurrentHashMap<>();

KnotClassDelegate(boolean isDevelopment, EnvType envType, KnotClassLoaderInterface itf, GameProvider provider) {
this.isDevelopment = isDevelopment;
Expand Down Expand Up @@ -238,6 +240,25 @@ Class<?> tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound
}
}

int pkgDelimiterPos = name.lastIndexOf('.');
String pkgString = pkgDelimiterPos > 0 ? name.substring(0, pkgDelimiterPos) : null;

if (pkgString != null) {
final boolean allowFromParentFinal = allowFromParent;
String denyReason = packageLoadDenyCache.computeIfAbsent(pkgString, pkgName -> {
return computePackageDenyLoadReason(pkgName, allowFromParentFinal);
});

if (denyReason != null && !denyReason.isEmpty()) {
throw new RuntimeException("Cannot load package " + pkgString + " " + denyReason);
}
}

String hideReason = hiddenClasses.get(name);
if (hideReason != null) {
throw new RuntimeException("Cannot load " + name + " " + hideReason);
}

byte[] input = getPostMixinClassByteArray(url, name);
if (input == null) return null;

Expand All @@ -247,8 +268,6 @@ Class<?> tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound

KnotClassDelegate.Metadata metadata = getMetadata(name, url);

int pkgDelimiterPos = name.lastIndexOf('.');

final String modId;

if (metadata.codeSource == null) {
Expand All @@ -268,19 +287,8 @@ Class<?> tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound
return c;
}

if (pkgDelimiterPos > 0) {
if (pkgString != null) {
// TODO: package definition stub
String pkgString = name.substring(0, pkgDelimiterPos);

final boolean allowFromParentFinal = allowFromParent;
Boolean permitted = packageSideCache.computeIfAbsent(pkgString, pkgName -> {
return computeCanLoadPackage(pkgName, allowFromParentFinal);
});

if (permitted != null && !permitted) {
throw new RuntimeException("Cannot load package " + pkgString + " in environment type " + envType);
}

Package pkg = itf.getPackage(pkgString);

if (pkg == null) {
Expand Down Expand Up @@ -310,28 +318,15 @@ Class<?> tryLoadClass(String name, boolean allowFromParent) throws ClassNotFound
return c;
}


private boolean shouldRerouteToParent(String name) {
return name.startsWith("org.slf4j.") || name.startsWith("org.apache.logging.log4j.");
}

boolean computeCanLoadPackage(String pkgName, boolean allowFromParent) {
private String computePackageDenyLoadReason(String pkgName, boolean allowFromParent) {
String fileName = pkgName + ".package-info";
try {
byte[] bytes = getRawClassByteArray(fileName, allowFromParent);
if (bytes == null) {
// No package-info class file
return true;
}

ClassReader reader = new ClassReader(bytes);

PackageStrippingData strippingData = new PackageStrippingData(QuiltLoaderImpl.ASM_VERSION, envType, modCodeSourceMap);
reader.accept(strippingData, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES);

return !strippingData.stripEntirePackage();
} catch (IOException e) {
throw new RuntimeException("Unable to load " + fileName, e);
}
String hideReason = hiddenClasses.get(fileName);
return hideReason != null ? hideReason : "";
}

Metadata getMetadata(String name, URL resourceURL) {
Expand Down Expand Up @@ -508,10 +503,6 @@ public byte[] getRawClassByteArray(String name, boolean allowFromParent) throws
}

public byte[] getRawClassByteArray(URL url, String name) throws IOException {
if (hiddenClasses.contains(name)) {
return null;
}

try (InputStream inputStream = (url != null ? url.openStream() : null)) {
if (inputStream == null) {
return null;
Expand All @@ -533,6 +524,14 @@ void setTransformCache(URL insideTransformCache) {
}

void setHiddenClasses(Set<String> hiddenClasses) {
Map<String, String> map = new HashMap<>();
for (String cl : hiddenClasses) {
map.put(cl, "unknown reason");
}
setHiddenClasses(map);
}

void setHiddenClasses(Map<String, String> hiddenClasses) {
this.hiddenClasses = hiddenClasses;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package org.quiltmc.loader.impl.transformer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.objectweb.asm.ClassVisitor;
import org.quiltmc.loader.api.Requires;
import org.quiltmc.loader.impl.util.QuiltLoaderInternal;
import org.quiltmc.loader.impl.util.QuiltLoaderInternalType;

import net.fabricmc.api.EnvType;

/** Contains string processing for both {@link PackageStrippingData} and {@link ClassStrippingData} */
@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL)
public abstract class AbstractStripData extends ClassVisitor {

protected final EnvType envType;
protected final Set<String> mods;

protected final List<String> denyLoadReasons = new ArrayList<>();

protected AbstractStripData(int api, EnvType envType, Set<String> mods) {
this(api, null, envType, mods);
}

protected AbstractStripData(int api, ClassVisitor classVisitor, EnvType envType, Set<String> mods) {
super(api, classVisitor);
this.envType = envType;
this.mods = mods;
}

/** @return What this represents - generally "package" or "class". */
protected abstract String type();

public List<String> getDenyLoadReasons() {
return Collections.unmodifiableList(denyLoadReasons);
}

public String summarizeDenyLoadReasons() {
if (denyLoadReasons.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder();
if (denyLoadReasons.size() == 1) {
sb.append("because ");
sb.append(denyLoadReasons.get(0));
} else {
sb.append("because:");
for (String reason : denyLoadReasons) {
sb.append("\n- ");
sb.append(reason);
}
}
return sb.toString();
}

/** Appends a client-only deny reason to {@link #denyLoadReasons}. */
protected void denyClientOnlyLoad() {
denyLoadReasons.add("the " + type() + " is annotated with @ClientOnly but we're on the dedicated server");
}

/** Appends a dedicated server only deny reason to {@link #denyLoadReasons}. */
protected void denyDediServerOnlyLoad() {
denyLoadReasons.add("the " + type() + " is annotated with @DedicatedServerOnly but we're on the client");
}

/** Checks to see if all mods given are in {@link #mods}, and appends a deny reason to {@link #denyLoadReasons} if
* any are missing. Assumes the annotation is {@link Requires} in the error message */
protected void checkHasAllMods(List<String> requiredMods) {
List<String> missingMods = new ArrayList<>();
for (String mod : requiredMods) {
if (!mods.contains(mod)) {
missingMods.add(mod);
}
}

checkHasAllMods(requiredMods, missingMods);
}

/** Checks to see if the missing mods list is empty, and appends a deny reason to {@link #denyLoadReasons} if it is
* not. Assumes the annotation is {@link Requires} in the error message */
protected void checkHasAllMods(List<String> requiredMods, List<String> missingMods) {
if (!missingMods.isEmpty()) {
StringBuilder all = new StringBuilder();
if (requiredMods.size() > 1) {
all.append("{");
}

for (String mod : requiredMods) {
if (all.length() > 1) {
all.append(", ");
}
all.append("\"");
all.append(mod);
all.append("\"");
}

if (requiredMods.size() > 1) {
all.append("}");
}

denyLoadReasons.add(
"the " + type() + " is annotated with @Requires(" + all + ") but the mods " + missingMods
+ " are not loaded!"
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
case REMOVED: {
QuiltMetadata qm = this_value_is_actually_nullable(result.getMetadata().get(QuiltMetadata.class));
if (qm != null) {
cache.hideClass(LoaderUtil.getClassNameFromTransformCache(qm.name));
cache.hideClass(LoaderUtil.getClassNameFromTransformCache(qm.name), "it was removed by a chasm transformer");
} else {
throw new UnsupportedOperationException("Cannot remove unknown class");
}
Expand Down
Loading

0 comments on commit 1c74011

Please sign in to comment.