diff options
Diffstat (limited to 'spark-fabric/src/main/java')
14 files changed, 714 insertions, 16 deletions
diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java index 7030680..51834fc 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricClassSourceLookup.java @@ -22,18 +22,35 @@ package me.lucko.spark.fabric; import com.google.common.collect.ImmutableMap; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.util.ClassFinder; +import me.lucko.spark.fabric.smap.MixinUtils; +import me.lucko.spark.fabric.smap.SourceMap; +import me.lucko.spark.fabric.smap.SourceMapProvider; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.objectweb.asm.Type; +import org.spongepowered.asm.mixin.FabricUtil; +import org.spongepowered.asm.mixin.extensibility.IMixinConfig; +import org.spongepowered.asm.mixin.transformer.Config; +import org.spongepowered.asm.mixin.transformer.meta.MixinMerged; + +import java.lang.reflect.Method; +import java.net.URI; import java.nio.file.Path; import java.util.Collection; import java.util.Map; public class FabricClassSourceLookup extends ClassSourceLookup.ByCodeSource { + + private final ClassFinder classFinder = new ClassFinder(); + private final SourceMapProvider smapProvider = new SourceMapProvider(); + private final Path modsDirectory; - private final Map<Path, String> pathToModMap; + private final Map<String, String> pathToModMap; public FabricClassSourceLookup() { FabricLoader loader = FabricLoader.getInstance(); @@ -43,7 +60,7 @@ public class FabricClassSourceLookup extends ClassSourceLookup.ByCodeSource { @Override public String identifyFile(Path path) { - String id = this.pathToModMap.get(path); + String id = this.pathToModMap.get(path.toAbsolutePath().normalize().toString()); if (id != null) { return id; } @@ -55,11 +72,141 @@ public class FabricClassSourceLookup extends ClassSourceLookup.ByCodeSource { return super.identifyFileName(this.modsDirectory.relativize(path).toString()); } - private static Map<Path, String> constructPathToModIdMap(Collection<ModContainer> mods) { - ImmutableMap.Builder<Path, String> builder = ImmutableMap.builder(); + @Override + public @Nullable String identify(MethodCall methodCall) throws Exception { + String className = methodCall.getClassName(); + String methodName = methodCall.getMethodName(); + String methodDesc = methodCall.getMethodDescriptor(); + + if (className.equals("native") || methodName.equals("<init>") || methodName.equals("<clinit>")) { + return null; + } + + Class<?> clazz = this.classFinder.findClass(className); + if (clazz == null) { + return null; + } + + Class<?>[] params = getParameterTypesForMethodDesc(methodDesc); + Method reflectMethod = clazz.getDeclaredMethod(methodName, params); + + MixinMerged mixinMarker = reflectMethod.getDeclaredAnnotation(MixinMerged.class); + if (mixinMarker == null) { + return null; + } + + return modIdFromMixinClass(mixinMarker.mixin()); + } + + @Override + public @Nullable String identify(MethodCallByLine methodCall) throws Exception { + String className = methodCall.getClassName(); + String methodName = methodCall.getMethodName(); + int lineNumber = methodCall.getLineNumber(); + + if (className.equals("native") || methodName.equals("<init>") || methodName.equals("<clinit>")) { + return null; + } + + SourceMap smap = this.smapProvider.getSourceMap(className); + if (smap == null) { + return null; + } + + int[] inputLineInfo = smap.getReverseLineMapping().get(lineNumber); + if (inputLineInfo == null || inputLineInfo.length == 0) { + return null; + } + + for (int fileInfoIds : inputLineInfo) { + SourceMap.FileInfo inputFileInfo = smap.getFileInfo().get(fileInfoIds); + if (inputFileInfo == null) { + continue; + } + + String path = inputFileInfo.path(); + if (path.endsWith(".java")) { + path = path.substring(0, path.length() - 5); + } + + String possibleMixinClassName = path.replace('/', '.'); + if (possibleMixinClassName.equals(className)) { + continue; + } + + return modIdFromMixinClass(possibleMixinClassName); + } + + return null; + } + + private static String modIdFromMixinClass(String mixinClassName) { + for (Config config : MixinUtils.getMixinConfigs().values()) { + IMixinConfig mixinConfig = config.getConfig(); + if (mixinClassName.startsWith(mixinConfig.getMixinPackage())) { + return mixinConfig.getDecoration(FabricUtil.KEY_MOD_ID); + } + } + return null; + } + + private Class<?>[] getParameterTypesForMethodDesc(String methodDesc) { + Type methodType = Type.getMethodType(methodDesc); + Class<?>[] params = new Class[methodType.getArgumentTypes().length]; + Type[] argumentTypes = methodType.getArgumentTypes(); + + for (int i = 0, argumentTypesLength = argumentTypes.length; i < argumentTypesLength; i++) { + Type argumentType = argumentTypes[i]; + params[i] = getClassFromType(argumentType); + } + + return params; + } + + private Class<?> getClassFromType(Type type) { + return switch (type.getSort()) { + case Type.VOID -> void.class; + case Type.BOOLEAN -> boolean.class; + case Type.CHAR -> char.class; + case Type.BYTE -> byte.class; + case Type.SHORT -> short.class; + case Type.INT -> int.class; + case Type.FLOAT -> float.class; + case Type.LONG -> long.class; + case Type.DOUBLE -> double.class; + case Type.ARRAY -> { + final Class<?> classFromType = getClassFromType(type.getElementType()); + Class<?> result = classFromType; + if (classFromType != null) { + for (int i = 0; i < type.getDimensions(); i++) { + result = result.arrayType(); + } + } + yield result; + } + case Type.OBJECT -> this.classFinder.findClass(type.getClassName()); + default -> null; + }; + } + + private static Map<String, String> constructPathToModIdMap(Collection<ModContainer> mods) { + ImmutableMap.Builder<String, String> builder = ImmutableMap.builder(); for (ModContainer mod : mods) { - Path path = mod.getRootPath().toAbsolutePath().normalize(); - builder.put(path, mod.getMetadata().getId()); + String modId = mod.getMetadata().getId(); + if (modId.equals("java")) { + continue; + } + + for (Path path : mod.getRootPaths()) { + URI uri = path.toUri(); + if (uri.getScheme().equals("jar") && path.toString().equals("/")) { // ZipFileSystem + String zipFilePath = path.getFileSystem().toString(); + builder.put(zipFilePath, modId); + } else { + builder.put(path.toAbsolutePath().normalize().toString(), modId); + } + + } } return builder.build(); } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java new file mode 100644 index 0000000..22794c2 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricExtraMetadataProvider.java @@ -0,0 +1,75 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) <luck@lucko.me> + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package me.lucko.spark.fabric; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import me.lucko.spark.common.platform.MetadataProvider; + +import net.minecraft.resource.ResourcePackManager; +import net.minecraft.resource.ResourcePackProfile; +import net.minecraft.resource.ResourcePackSource; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class FabricExtraMetadataProvider implements MetadataProvider { + + private final ResourcePackManager resourcePackManager; + + public FabricExtraMetadataProvider(ResourcePackManager resourcePackManager) { + this.resourcePackManager = resourcePackManager; + } + + @Override + public Map<String, JsonElement> get() { + Map<String, JsonElement> metadata = new LinkedHashMap<>(); + metadata.put("datapacks", datapackMetadata()); + return metadata; + } + + private JsonElement datapackMetadata() { + JsonObject datapacks = new JsonObject(); + for (ResourcePackProfile profile : this.resourcePackManager.getEnabledProfiles()) { + JsonObject obj = new JsonObject(); + obj.addProperty("name", profile.getDisplayName().getString()); + obj.addProperty("description", profile.getDescription().getString()); + obj.addProperty("source", resourcePackSource(profile.getSource())); + datapacks.add(profile.getName(), obj); + } + return datapacks; + } + + private static String resourcePackSource(ResourcePackSource source) { + if (source == ResourcePackSource.NONE) { + return "none"; + } else if (source == ResourcePackSource.BUILTIN) { + return "builtin"; + } else if (source == ResourcePackSource.WORLD) { + return "world"; + } else if (source == ResourcePackSource.SERVER) { + return "server"; + } else { + return "unknown"; + } + } +} diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricServerConfigProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricServerConfigProvider.java index 18079d3..325a324 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricServerConfigProvider.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricServerConfigProvider.java @@ -23,14 +23,14 @@ package me.lucko.spark.fabric; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import me.lucko.spark.common.platform.serverconfig.AbstractServerConfigProvider; import me.lucko.spark.common.platform.serverconfig.ConfigParser; import me.lucko.spark.common.platform.serverconfig.PropertiesConfigParser; +import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; import java.util.Collection; import java.util.Map; -public class FabricServerConfigProvider extends AbstractServerConfigProvider { +public class FabricServerConfigProvider extends ServerConfigProvider { /** A map of provided files and their type */ private static final Map<String, ConfigParser> FILES; diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricWorldInfoProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricWorldInfoProvider.java index f2f7b96..156db89 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricWorldInfoProvider.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/FabricWorldInfoProvider.java @@ -40,6 +40,7 @@ import net.minecraft.server.MinecraftServer; import net.minecraft.server.world.ServerEntityManager; import net.minecraft.server.world.ServerWorld; import net.minecraft.util.math.ChunkPos; +import net.minecraft.world.entity.EntityIndex; import net.minecraft.world.entity.EntityTrackingSection; import net.minecraft.world.entity.SectionedEntityCache; @@ -72,8 +73,25 @@ public abstract class FabricWorldInfoProvider implements WorldInfoProvider { } @Override - public Result<FabricChunkInfo> poll() { - Result<FabricChunkInfo> data = new Result<>(); + public CountsResult pollCounts() { + int players = this.server.getCurrentPlayerCount(); + int entities = 0; + int chunks = 0; + + for (ServerWorld world : this.server.getWorlds()) { + ServerEntityManager<Entity> entityManager = ((ServerWorldAccessor) world).getEntityManager(); + EntityIndex<?> entityIndex = ((ServerEntityManagerAccessor) entityManager).getIndex(); + + entities += entityIndex.size(); + chunks += world.getChunkManager().getLoadedChunkCount(); + } + + return new CountsResult(players, entities, -1, chunks); + } + + @Override + public ChunksResult<FabricChunkInfo> pollChunks() { + ChunksResult<FabricChunkInfo> data = new ChunksResult<>(); for (ServerWorld world : this.server.getWorlds()) { ServerEntityManager<Entity> entityManager = ((ServerWorldAccessor) world).getEntityManager(); @@ -95,8 +113,24 @@ public abstract class FabricWorldInfoProvider implements WorldInfoProvider { } @Override - public Result<FabricChunkInfo> poll() { - Result<FabricChunkInfo> data = new Result<>(); + public CountsResult pollCounts() { + ClientWorld world = this.client.world; + if (world == null) { + return null; + } + + ClientEntityManager<Entity> entityManager = ((ClientWorldAccessor) world).getEntityManager(); + EntityIndex<?> entityIndex = ((ClientEntityManagerAccessor) entityManager).getIndex(); + + int entities = entityIndex.size(); + int chunks = world.getChunkManager().getLoadedChunkCount(); + + return new CountsResult(-1, entities, -1, chunks); + } + + @Override + public ChunksResult<FabricChunkInfo> pollChunks() { + ChunksResult<FabricChunkInfo> data = new ChunksResult<>(); ClientWorld world = this.client.world; if (world == null) { diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientEntityManagerAccessor.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientEntityManagerAccessor.java index 88c9521..994c9a3 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientEntityManagerAccessor.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ClientEntityManagerAccessor.java @@ -22,6 +22,7 @@ package me.lucko.spark.fabric.mixin; import net.minecraft.client.world.ClientEntityManager; import net.minecraft.entity.Entity; +import net.minecraft.world.entity.EntityIndex; import net.minecraft.world.entity.SectionedEntityCache; import org.spongepowered.asm.mixin.Mixin; @@ -33,4 +34,7 @@ public interface ClientEntityManagerAccessor { @Accessor SectionedEntityCache<Entity> getCache(); + @Accessor + EntityIndex<?> getIndex(); + } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerEntityManagerAccessor.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerEntityManagerAccessor.java index 160a12b..2c67502 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerEntityManagerAccessor.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/mixin/ServerEntityManagerAccessor.java @@ -22,6 +22,7 @@ package me.lucko.spark.fabric.mixin; import net.minecraft.entity.Entity; import net.minecraft.server.world.ServerEntityManager; +import net.minecraft.world.entity.EntityIndex; import net.minecraft.world.entity.SectionedEntityCache; import org.spongepowered.asm.mixin.Mixin; @@ -33,4 +34,7 @@ public interface ServerEntityManagerAccessor { @Accessor SectionedEntityCache<Entity> getCache(); + @Accessor + EntityIndex<?> getIndex(); + } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java index 0ef6620..faf4eef 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricClientSparkPlugin.java @@ -28,12 +28,14 @@ import com.mojang.brigadier.suggestion.SuggestionProvider; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import me.lucko.spark.common.platform.MetadataProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.platform.world.WorldInfoProvider; import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; import me.lucko.spark.fabric.FabricCommandSender; +import me.lucko.spark.fabric.FabricExtraMetadataProvider; import me.lucko.spark.fabric.FabricPlatformInfo; import me.lucko.spark.fabric.FabricSparkMod; import me.lucko.spark.fabric.FabricTickHook; @@ -138,6 +140,11 @@ public class FabricClientSparkPlugin extends FabricSparkPlugin implements Comman } @Override + public MetadataProvider createExtraMetadataProvider() { + return new FabricExtraMetadataProvider(this.minecraft.getResourcePackManager()); + } + + @Override public WorldInfoProvider createWorldInfoProvider() { return new FabricWorldInfoProvider.Client(this.minecraft); } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java index f840f5e..1606d57 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricServerSparkPlugin.java @@ -30,6 +30,7 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import me.lucko.fabric.api.permissions.v0.Permissions; import me.lucko.spark.common.monitor.ping.PlayerPingProvider; +import me.lucko.spark.common.platform.MetadataProvider; import me.lucko.spark.common.platform.PlatformInfo; import me.lucko.spark.common.platform.serverconfig.ServerConfigProvider; import me.lucko.spark.common.platform.world.WorldInfoProvider; @@ -37,6 +38,7 @@ import me.lucko.spark.common.sampler.ThreadDumper; import me.lucko.spark.common.tick.TickHook; import me.lucko.spark.common.tick.TickReporter; import me.lucko.spark.fabric.FabricCommandSender; +import me.lucko.spark.fabric.FabricExtraMetadataProvider; import me.lucko.spark.fabric.FabricPlatformInfo; import me.lucko.spark.fabric.FabricPlayerPingProvider; import me.lucko.spark.fabric.FabricServerConfigProvider; @@ -117,8 +119,15 @@ public class FabricServerSparkPlugin extends FabricSparkPlugin implements Comman @Override public boolean hasPermission(CommandOutput sender, String permission) { - if (sender instanceof PlayerEntity) { - return Permissions.check(((PlayerEntity) sender), permission, 4); + if (sender instanceof PlayerEntity player) { + return Permissions.getPermissionValue(player, permission).orElseGet(() -> { + MinecraftServer server = player.getServer(); + if (server != null && server.isHost(player.getGameProfile())) { + return true; + } + + return player.hasPermissionLevel(4); + }); } else { return true; } @@ -163,6 +172,11 @@ public class FabricServerSparkPlugin extends FabricSparkPlugin implements Comman } @Override + public MetadataProvider createExtraMetadataProvider() { + return new FabricExtraMetadataProvider(this.server.getDataPackManager()); + } + + @Override public WorldInfoProvider createWorldInfoProvider() { return new FabricWorldInfoProvider.Server(this.server); } diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkMixinPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkMixinPlugin.java new file mode 100644 index 0000000..cfc8c95 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkMixinPlugin.java @@ -0,0 +1,71 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) <luck@lucko.me> + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package me.lucko.spark.fabric.plugin; + +import me.lucko.spark.fabric.smap.SourceDebugCache; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinInfo; +import org.spongepowered.asm.mixin.transformer.IMixinTransformer; +import org.spongepowered.asm.mixin.transformer.ext.Extensions; +import org.spongepowered.asm.mixin.transformer.ext.IExtension; +import org.spongepowered.asm.mixin.transformer.ext.ITargetClassContext; + +import java.util.List; +import java.util.Set; + +public class FabricSparkMixinPlugin implements IMixinConfigPlugin, IExtension { + + private static final Logger LOGGER = LogManager.getLogger("spark"); + + @Override + public void onLoad(String mixinPackage) { + Object activeTransformer = MixinEnvironment.getCurrentEnvironment().getActiveTransformer(); + if (activeTransformer instanceof IMixinTransformer transformer && transformer.getExtensions() instanceof Extensions extensions) { + extensions.add(this); + } else { + LOGGER.error( + "Failed to initialize SMAP parser for spark profiler. " + + "Mod information for mixin injected methods is now only available with the async-profiler engine." + ); + } + } + + @Override + public void export(MixinEnvironment env, String name, boolean force, ClassNode classNode) { + SourceDebugCache.put(name, classNode); + } + + // noop + @Override public String getRefMapperConfig() { return null; } + @Override public boolean shouldApplyMixin(String targetClassName, String mixinClassName) { return true; } + @Override public void acceptTargets(Set<String> myTargets, Set<String> otherTargets) { } + @Override public List<String> getMixins() { return null; } + @Override public void preApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } + @Override public void postApply(String targetClassName, ClassNode targetClass, String mixinClassName, IMixinInfo mixinInfo) { } + @Override public boolean checkActive(MixinEnvironment environment) { return true; } + @Override public void preApply(ITargetClassContext context) { } + @Override public void postApply(ITargetClassContext context) { } + +} diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java index 3126f28..9a03b4e 100644 --- a/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/plugin/FabricSparkPlugin.java @@ -34,11 +34,14 @@ import com.mojang.brigadier.tree.LiteralCommandNode; import me.lucko.spark.common.SparkPlatform; import me.lucko.spark.common.SparkPlugin; import me.lucko.spark.common.command.sender.CommandSender; -import me.lucko.spark.common.util.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.ClassSourceLookup; +import me.lucko.spark.common.sampler.source.SourceMetadata; import me.lucko.spark.common.util.SparkThreadFactory; import me.lucko.spark.fabric.FabricClassSourceLookup; import me.lucko.spark.fabric.FabricSparkMod; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.metadata.Person; import net.minecraft.server.command.CommandOutput; import org.apache.logging.log4j.LogManager; @@ -46,10 +49,12 @@ import org.apache.logging.log4j.Logger; import java.nio.file.Path; import java.util.Arrays; +import java.util.Collection; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.logging.Level; +import java.util.stream.Collectors; public abstract class FabricSparkPlugin implements SparkPlugin { @@ -110,6 +115,18 @@ public abstract class FabricSparkPlugin implements SparkPlugin { return new FabricClassSourceLookup(); } + @Override + public Collection<SourceMetadata> getKnownSources() { + return SourceMetadata.gather( + FabricLoader.getInstance().getAllMods(), + mod -> mod.getMetadata().getId(), + mod -> mod.getMetadata().getVersion().getFriendlyString(), + mod -> mod.getMetadata().getAuthors().stream() + .map(Person::getName) + .collect(Collectors.joining(", ")) + ); + } + protected CompletableFuture<Suggestions> generateSuggestions(CommandSender sender, String[] args, SuggestionsBuilder builder) { SuggestionsBuilder suggestions; diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/MixinUtils.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/MixinUtils.java new file mode 100644 index 0000000..ebf2766 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/MixinUtils.java @@ -0,0 +1,52 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) <luck@lucko.me> + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package me.lucko.spark.fabric.smap; + +import org.spongepowered.asm.mixin.transformer.Config; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +public enum MixinUtils { + ; + + private static final Map<String, Config> MIXIN_CONFIGS; + + static { + Map<String, Config> configs; + try { + Field allConfigsField = Config.class.getDeclaredField("allConfigs"); + allConfigsField.setAccessible(true); + + //noinspection unchecked + configs = (Map<String, Config>) allConfigsField.get(null); + } catch (Exception e) { + e.printStackTrace(); + configs = new HashMap<>(); + } + MIXIN_CONFIGS = configs; + } + + public static Map<String, Config> getMixinConfigs() { + return MIXIN_CONFIGS; + } +} diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceDebugCache.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceDebugCache.java new file mode 100644 index 0000000..88adae6 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceDebugCache.java @@ -0,0 +1,87 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) <luck@lucko.me> + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package me.lucko.spark.fabric.smap; + +import org.objectweb.asm.tree.ClassNode; +import org.spongepowered.asm.service.IClassBytecodeProvider; +import org.spongepowered.asm.service.MixinService; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Caches the lookup of class -> source debug info for classes loaded on the JVM. + * + * The {@link me.lucko.spark.fabric.plugin.FabricSparkMixinPlugin} also supplements this cache with + * extra information as classes are exported. + */ +public enum SourceDebugCache { + ; + + // class name -> smap + private static final Map<String, SmapValue> CACHE = new ConcurrentHashMap<>(); + + public static void put(String className, ClassNode node) { + if (className == null || node == null) { + return; + } + className = className.replace('/', '.'); + CACHE.put(className, SmapValue.of(node.sourceDebug)); + } + + public static String getSourceDebugInfo(String className) { + SmapValue cached = CACHE.get(className); + if (cached != null) { + return cached.value(); + } + + try { + IClassBytecodeProvider provider = MixinService.getService().getBytecodeProvider(); + ClassNode classNode = provider.getClassNode(className.replace('.', '/')); + + if (classNode != null) { + put(className, classNode); + return classNode.sourceDebug; + } + + } catch (Exception e) { + // ignore + } + + CACHE.put(className, SmapValue.NULL); + return null; + } + + private record SmapValue(String value) { + static final SmapValue NULL = new SmapValue(null); + + static SmapValue of(String value) { + if (value == null) { + return NULL; + } else { + return new SmapValue(value); + } + } + + } + +} diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMap.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMap.java new file mode 100644 index 0000000..5105a26 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMap.java @@ -0,0 +1,133 @@ +/* + * SMAPSourceDebugExtension.java - Parse source debug extensions and + * enhance stack traces. + * + * Copyright (c) 2012 Michael Schierl + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND THE CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package me.lucko.spark.fabric.smap; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Utility class to parse "SMAP" (source map) information from loaded Java classes. + * + * @author <a href="https://stackoverflow.com/a/11299757">Michael Schierl</a> + */ +public class SourceMap { + + private final String generatedFileName; + private final String firstStratum; + private final Map<Integer, FileInfo> fileinfo = new HashMap<>(); + private final Map<Integer, int[]> reverseLineMapping = new HashMap<>(); + + private static final Pattern LINE_INFO_PATTERN = Pattern.compile("([0-9]+)(?:#([0-9]+))?(?:,([0-9]+))?:([0-9]+)(?:,([0-9]+))?"); + + public SourceMap(String value) { + String[] lines = value.split("\n"); + if (!lines[0].equals("SMAP") || !lines[3].startsWith("*S ") || !lines[4].equals("*F")) { + throw new IllegalArgumentException(value); + } + + this.generatedFileName = lines[1]; + this.firstStratum = lines[3].substring(3); + + int idx = 5; + while (!lines[idx].startsWith("*")) { + String infoline = lines[idx++]; + String path = null; + + if (infoline.startsWith("+ ")) { + path = lines[idx++]; + infoline = infoline.substring(2); + } + + int pos = infoline.indexOf(" "); + int filenum = Integer.parseInt(infoline.substring(0, pos)); + String name = infoline.substring(pos + 1); + + this.fileinfo.put(filenum, new FileInfo(name, path == null ? name : path)); + } + + if (lines[idx].equals("*L")) { + idx++; + int lastLFI = 0; + + while (!lines[idx].startsWith("*")) { + Matcher m = LINE_INFO_PATTERN.matcher(lines[idx++]); + if (!m.matches()) { + throw new IllegalArgumentException(lines[idx - 1]); + } + + int inputStartLine = Integer.parseInt(m.group(1)); + int lineFileID = m.group(2) == null ? lastLFI : Integer.parseInt(m.group(2)); + int repeatCount = m.group(3) == null ? 1 : Integer.parseInt(m.group(3)); + int outputStartLine = Integer.parseInt(m.group(4)); + int outputLineIncrement = m.group(5) == null ? 1 : Integer.parseInt(m.group(5)); + + for (int i = 0; i < repeatCount; i++) { + int[] inputMapping = new int[] { lineFileID, inputStartLine + i }; + int baseOL = outputStartLine + i * outputLineIncrement; + + for (int ol = baseOL; ol < baseOL + outputLineIncrement; ol++) { + if (!this.reverseLineMapping.containsKey(ol)) { + this.reverseLineMapping.put(ol, inputMapping); + } + } + } + + lastLFI = lineFileID; + } + } + } + + public String getGeneratedFileName() { + return this.generatedFileName; + } + + public String getFirstStratum() { + return this.firstStratum; + } + + public Map<Integer, FileInfo> getFileInfo() { + return this.fileinfo; + } + + public Map<Integer, int[]> getReverseLineMapping() { + return this.reverseLineMapping; + } + + public record FileInfo(String name, String path) { } +}
\ No newline at end of file diff --git a/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMapProvider.java b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMapProvider.java new file mode 100644 index 0000000..1a4f246 --- /dev/null +++ b/spark-fabric/src/main/java/me/lucko/spark/fabric/smap/SourceMapProvider.java @@ -0,0 +1,53 @@ +/* + * This file is part of spark. + * + * Copyright (c) lucko (Luck) <luck@lucko.me> + * Copyright (c) contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +package me.lucko.spark.fabric.smap; + +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +public class SourceMapProvider { + private final Map<String, SourceMap> cache = new HashMap<>(); + + public @Nullable SourceMap getSourceMap(String className) { + if (this.cache.containsKey(className)) { + return this.cache.get(className); + } + + SourceMap smap = null; + try { + String value = SourceDebugCache.getSourceDebugInfo(className); + if (value != null) { + value = value.replaceAll("\r\n?", "\n"); + if (value.startsWith("SMAP\n")) { + smap = new SourceMap(value); + } + } + } catch (Exception e) { + // ignore + } + + this.cache.put(className, smap); + return smap; + } + +} |