diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml index 192980d42d7..49bd2400179 100644 --- a/.github/workflows/build-pr.yml +++ b/.github/workflows/build-pr.yml @@ -21,4 +21,4 @@ jobs: run: | git config --global user.email "no-reply@github.com" git config --global user.name "Github Actions" - ./gradlew classes \ No newline at end of file + ./gradlew check \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index dc3efa6a041..a0e84ce2f36 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,6 @@ +import com.github.spotbugs.snom.Confidence +import com.github.spotbugs.snom.Effort +import com.github.spotbugs.snom.SpotBugsTask import java.util.* plugins { @@ -6,6 +9,7 @@ plugins { id("minestom.native-conventions") alias(libs.plugins.blossom) signing + id("com.github.spotbugs") version "6.0.15" } var baseVersion by extra("1.3.2") @@ -47,6 +51,15 @@ java { withSourcesJar() } +spotbugs { + effort = Effort.MAX + reportLevel = Confidence.LOW + toolVersion = "4.8.5" + ignoreFailures = false + showStackTraces = true + showProgress = true +} + tasks { jar { manifest { @@ -77,7 +90,18 @@ tasks { minHeapSize = "512m" maxHeapSize = "1024m" } + withType { + reports.create("html") { + required = true + outputLocation = file("${layout.buildDirectory.get()}/reports/spotbugs.html") + setStylesheet("fancy-hist.xsl") + } + reports.create("xml") { + required = true + outputLocation = file("${layout.buildDirectory.get()}/reports/spotbugs.xml") + } + } } @@ -123,5 +147,9 @@ dependencies { // BStats api(libs.bstats.base) + + spotbugsPlugins("com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0") + implementation("com.google.code.findbugs:annotations:3.0.1") + } diff --git a/src/main/java/net/minestom/server/ServerProcessImpl.java b/src/main/java/net/minestom/server/ServerProcessImpl.java index 4c1a9de9c88..ed114cd86d2 100644 --- a/src/main/java/net/minestom/server/ServerProcessImpl.java +++ b/src/main/java/net/minestom/server/ServerProcessImpl.java @@ -238,7 +238,7 @@ public void start(@NotNull SocketAddress socketAddress) { extension.start(); extension.gotoPreInit(); - LOGGER.info("Starting " + MinecraftServer.getBrandName() + " server."); + LOGGER.info("Starting {} server.", MinecraftServer.getBrandName()); extension.gotoInit(); @@ -255,7 +255,7 @@ public void start(@NotNull SocketAddress socketAddress) { extension.gotoPostInit(); - LOGGER.info(MinecraftServer.getBrandName() + " server started successfully."); + LOGGER.info("{} server started successfully.", MinecraftServer.getBrandName()); if (ServerFlag.ATTRIBUTES_ENABLED) { Attributes.registerAttributes(); @@ -281,7 +281,7 @@ public void start(@NotNull SocketAddress socketAddress) { public void stop() { if (!stopped.compareAndSet(false, true)) return; - LOGGER.info("Stopping " + MinecraftServer.getBrandName() + " server."); + LOGGER.info("Stopping {} server.", MinecraftServer.getBrandName()); LOGGER.info("Unloading all extensions."); extension.shutdown(); scheduler.shutdown(); @@ -292,7 +292,7 @@ public void stop() { MinestomTerminal.stop(); dispatcher.shutdown(); this.metrics.shutdown(); - LOGGER.info(MinecraftServer.getBrandName() + " server stopped successfully."); + LOGGER.info("{MinecraftServer.getBrandName()} server stopped successfully."); } @Override diff --git a/src/main/java/net/minestom/server/collision/BlockCollision.java b/src/main/java/net/minestom/server/collision/BlockCollision.java index 3d3595421fe..2984504547b 100644 --- a/src/main/java/net/minestom/server/collision/BlockCollision.java +++ b/src/main/java/net/minestom/server/collision/BlockCollision.java @@ -184,7 +184,7 @@ private static PhysicsResult computePhysics(@NotNull BoundingBox boundingBox, return new PhysicsResult(finalPos, new Vec(remainingX, remainingY, remainingZ), collisionY, collisionX, collisionY, collisionZ, - Vec.ZERO, null, null, false, finalResult); + Vec.ZERO, new Point[3], new Shape[3], false, finalResult); } private static void slowPhysics(@NotNull BoundingBox boundingBox, diff --git a/src/main/java/net/minestom/server/collision/BoundingBox.java b/src/main/java/net/minestom/server/collision/BoundingBox.java index 0c714422375..4bc8665cf05 100644 --- a/src/main/java/net/minestom/server/collision/BoundingBox.java +++ b/src/main/java/net/minestom/server/collision/BoundingBox.java @@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Iterator; +import java.util.Objects; /** * See https://wiki.vg/Entity_metadata#Mobs_2 @@ -260,4 +261,9 @@ public boolean equals(Object o) { Vec dimensions = max.sub(min); return new BoundingBox(dimensions.x(), dimensions.y(), dimensions.z(), min); } + + @Override + public int hashCode() { + return Objects.hash(width, height, depth, offset, relativeEnd); + } } diff --git a/src/main/java/net/minestom/server/command/CommandParserImpl.java b/src/main/java/net/minestom/server/command/CommandParserImpl.java index 07c3125f45d..18b4d67b133 100644 --- a/src/main/java/net/minestom/server/command/CommandParserImpl.java +++ b/src/main/java/net/minestom/server/command/CommandParserImpl.java @@ -319,7 +319,7 @@ record ValidExecutableCmd(CommandCondition condition, CommandExecutor globalList executor().apply(sender, context); return new ExecutionResultImpl(ExecutableCommand.Result.Type.SUCCESS, context.getReturnData()); } catch (Exception e) { - LOGGER.error("An exception was encountered while executing command: " + input(), e); + LOGGER.error("An exception was encountered while executing command: {}", new Object[]{input()}, e); return ExecutionResultImpl.EXECUTOR_EXCEPTION; } } diff --git a/src/main/java/net/minestom/server/entity/metadata/EntityMeta.java b/src/main/java/net/minestom/server/entity/metadata/EntityMeta.java index c1a4ae454e2..dd84ae4d9b3 100644 --- a/src/main/java/net/minestom/server/entity/metadata/EntityMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/EntityMeta.java @@ -26,7 +26,7 @@ public class EntityMeta { private final WeakReference entityRef; protected final Metadata metadata; - public EntityMeta(@Nullable Entity entity, @NotNull Metadata metadata) { + public EntityMeta(@NotNull Entity entity, @NotNull Metadata metadata) { this.entityRef = new WeakReference<>(entity); this.metadata = metadata; } diff --git a/src/main/java/net/minestom/server/entity/metadata/other/InteractionMeta.java b/src/main/java/net/minestom/server/entity/metadata/other/InteractionMeta.java index 42ba7d2be68..8f813334421 100644 --- a/src/main/java/net/minestom/server/entity/metadata/other/InteractionMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/other/InteractionMeta.java @@ -4,14 +4,13 @@ import net.minestom.server.entity.Metadata; import net.minestom.server.entity.metadata.EntityMeta; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; public class InteractionMeta extends EntityMeta { public static final byte OFFSET = EntityMeta.MAX_OFFSET; public static final byte MAX_OFFSET = OFFSET + 3; - public InteractionMeta(@Nullable Entity entity, @NotNull Metadata metadata) { + public InteractionMeta(@NotNull Entity entity, @NotNull Metadata metadata) { super(entity, metadata); } diff --git a/src/main/java/net/minestom/server/entity/metadata/villager/VillagerMeta.java b/src/main/java/net/minestom/server/entity/metadata/villager/VillagerMeta.java index dd0b7cd05fc..8241a95e878 100644 --- a/src/main/java/net/minestom/server/entity/metadata/villager/VillagerMeta.java +++ b/src/main/java/net/minestom/server/entity/metadata/villager/VillagerMeta.java @@ -78,7 +78,7 @@ public enum Type { SWAMP, TAIGA; - public final static Type[] VALUES = values(); + public static final Type[] VALUES = values(); } public enum Profession { @@ -99,7 +99,7 @@ public enum Profession { TOOLSMITH, WEAPONSMITH; - public final static Profession[] VALUES = values(); + public static final Profession[] VALUES = values(); } public enum Level { @@ -109,7 +109,7 @@ public enum Level { EXPERT, MASTER; - public final static Level[] VALUES = values(); + public static final Level[] VALUES = values(); } } diff --git a/src/main/java/net/minestom/server/event/player/PlayerPluginMessageEvent.java b/src/main/java/net/minestom/server/event/player/PlayerPluginMessageEvent.java index aee14e3d886..0ded460d468 100644 --- a/src/main/java/net/minestom/server/event/player/PlayerPluginMessageEvent.java +++ b/src/main/java/net/minestom/server/event/player/PlayerPluginMessageEvent.java @@ -5,6 +5,8 @@ import net.minestom.server.network.packet.client.common.ClientPluginMessagePacket; import org.jetbrains.annotations.NotNull; +import java.nio.charset.StandardCharsets; + /** * Called when a player send {@link ClientPluginMessagePacket}. */ @@ -47,7 +49,7 @@ public byte[] getMessage() { */ @NotNull public String getMessageString() { - return new String(message); + return new String(message, StandardCharsets.UTF_8); } @Override diff --git a/src/main/java/net/minestom/server/extensions/ExtensionManager.java b/src/main/java/net/minestom/server/extensions/ExtensionManager.java index 6985fb8baa6..dc3ac77ad8f 100644 --- a/src/main/java/net/minestom/server/extensions/ExtensionManager.java +++ b/src/main/java/net/minestom/server/extensions/ExtensionManager.java @@ -33,6 +33,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.*; import java.util.stream.Collectors; @@ -394,7 +395,7 @@ private Extension loadExtension(@NotNull DiscoveredExtension discoveredExtension LOGGER.info("Found indev folders for extension. Adding to list of discovered extensions."); final String extensionClasses = System.getProperty(INDEV_CLASSES_FOLDER); final String extensionResources = System.getProperty(INDEV_RESOURCES_FOLDER); - try (InputStreamReader reader = new InputStreamReader(new FileInputStream(new File(extensionResources, "extension.json")))) { + try (InputStreamReader reader = new InputStreamReader(new FileInputStream(new File(extensionResources, "extension.json")), StandardCharsets.UTF_8)) { DiscoveredExtension extension = GSON.fromJson(reader, DiscoveredExtension.class); extension.files.add(new File(extensionClasses).toURI().toURL()); extension.files.add(new File(extensionResources).toURI().toURL()); @@ -427,7 +428,7 @@ private Extension loadExtension(@NotNull DiscoveredExtension discoveredExtension if (entry == null) throw new IllegalStateException("Missing extension.json in extension " + file.getName() + "."); - InputStreamReader reader = new InputStreamReader(f.getInputStream(entry)); + InputStreamReader reader = new InputStreamReader(f.getInputStream(entry), StandardCharsets.UTF_8); // Initialize DiscoveredExtension from GSON. DiscoveredExtension extension = GSON.fromJson(reader, DiscoveredExtension.class); diff --git a/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java b/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java index 10e47b03da0..a1c002bdbef 100644 --- a/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java +++ b/src/main/java/net/minestom/server/extras/velocity/VelocityProxy.java @@ -7,6 +7,7 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.Key; import java.security.MessageDigest; @@ -38,7 +39,7 @@ public static void enable(@NotNull String secret) { Check.stateCondition(MojangAuth.isEnabled(), "Velocity modern forwarding should not be enabled with MojangAuth"); VelocityProxy.enabled = true; - VelocityProxy.key = new SecretKeySpec(secret.getBytes(), MAC_ALGORITHM); + VelocityProxy.key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), MAC_ALGORITHM); } /** diff --git a/src/main/java/net/minestom/server/instance/Section.java b/src/main/java/net/minestom/server/instance/Section.java index e951eeb110e..c5f50492bb7 100644 --- a/src/main/java/net/minestom/server/instance/Section.java +++ b/src/main/java/net/minestom/server/instance/Section.java @@ -11,7 +11,7 @@ import static net.minestom.server.instance.light.LightCompute.emptyContent; import static net.minestom.server.network.NetworkBuffer.SHORT; -public final class Section implements NetworkBuffer.Writer { +public final class Section implements NetworkBuffer.Writer, Cloneable { private final Palette blockPalette; private final Palette biomePalette; private final Light skyLight; diff --git a/src/main/java/net/minestom/server/instance/palette/FilledPalette.java b/src/main/java/net/minestom/server/instance/palette/FilledPalette.java index 669d098f66b..3b6612ecbaf 100644 --- a/src/main/java/net/minestom/server/instance/palette/FilledPalette.java +++ b/src/main/java/net/minestom/server/instance/palette/FilledPalette.java @@ -9,7 +9,7 @@ /** * Palette containing a single value. Useful for both empty and full palettes. */ -record FilledPalette(byte dim, int value) implements SpecializedPalette.Immutable { +record FilledPalette(byte dim, int value) implements SpecializedPalette.Immutable, Cloneable { @Override public int get(int x, int y, int z) { return value; diff --git a/src/main/java/net/minestom/server/item/rule/VanillaStackingRule.java b/src/main/java/net/minestom/server/item/rule/VanillaStackingRule.java index 496d23bb4a9..dd88d648a40 100644 --- a/src/main/java/net/minestom/server/item/rule/VanillaStackingRule.java +++ b/src/main/java/net/minestom/server/item/rule/VanillaStackingRule.java @@ -37,4 +37,9 @@ public boolean equals(Object obj) { if (this == obj) return true; return obj != null && getClass() == obj.getClass(); } + + @Override + public int hashCode() { + return super.hashCode(); + } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java index 25978714838..58d66e05744 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java @@ -75,12 +75,14 @@ private EntitySoundEffectPacket(@NotNull EntitySoundEffectPacket packet) { @Override public void write(@NotNull NetworkBuffer writer) { - if (soundEvent != null) { + if (soundEvent != null && soundName == null) { writer.write(VAR_INT, soundEvent.id() + 1); - } else { + } else if (soundName != null && soundEvent == null){ writer.write(VAR_INT, 0); writer.write(STRING, soundName); writer.writeOptional(FLOAT, range); + } else { + return; } writer.write(VAR_INT, AdventurePacketConvertor.getSoundSourceValue(source)); writer.write(VAR_INT, entityId); diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ServerDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ServerDataPacket.java index 3f9db24ca9e..bbd4fc9c697 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ServerDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ServerDataPacket.java @@ -9,7 +9,7 @@ import static net.minestom.server.network.NetworkBuffer.*; -public record ServerDataPacket(@Nullable Component motd, byte @Nullable [] iconBase64, +public record ServerDataPacket(@NotNull Component motd, byte @Nullable [] iconBase64, boolean enforcesSecureChat) implements ServerPacket.Play { public ServerDataPacket(@NotNull NetworkBuffer reader) { this(reader.read(COMPONENT), reader.readOptional(BYTE_ARRAY), diff --git a/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java index 831c157a42d..577ad5594aa 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java @@ -28,7 +28,7 @@ public record SoundEffectPacket( ) implements ServerPacket.Play { public SoundEffectPacket { - Check.argCondition(soundEvent == null && soundName == null, "soundEvent and soundName cannot both be null"); + Check.argCondition(soundEvent == null || soundName == null, "soundEvent and soundName cannot both be null"); Check.argCondition(soundEvent != null && soundName != null, "soundEvent and soundName cannot both be present"); Check.argCondition(soundName == null && range != null, "range cannot be present if soundName is null"); } @@ -81,12 +81,14 @@ private SoundEffectPacket(@NotNull SoundEffectPacket packet) { @Override public void write(@NotNull NetworkBuffer writer) { - if (soundEvent != null) { + if (soundEvent != null && soundName == null) { writer.write(VAR_INT, soundEvent.id() + 1); - } else { + } else if (soundName != null && soundEvent == null) { writer.write(VAR_INT, 0); writer.write(STRING, soundName); writer.writeOptional(FLOAT, range); + } else { + return; } writer.write(VAR_INT, AdventurePacketConvertor.getSoundSourceValue(source)); writer.write(INT, x * 8); diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java index 787b8a5b374..fdf376b255a 100644 --- a/src/main/java/net/minestom/server/registry/Registry.java +++ b/src/main/java/net/minestom/server/registry/Registry.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; @@ -83,7 +84,7 @@ public static Map> load(Resource resource) { Map> map = new HashMap<>(); try (InputStream resourceStream = Registry.class.getClassLoader().getResourceAsStream(resource.name)) { Check.notNull(resourceStream, "Resource {0} does not exist!", resource); - try (JsonReader reader = new JsonReader(new InputStreamReader(resourceStream))) { + try (JsonReader reader = new JsonReader(new InputStreamReader(resourceStream, StandardCharsets.UTF_8))) { reader.beginObject(); while (reader.hasNext()) map.put(reader.nextName(), (Map) readObject(reader)); reader.endObject(); @@ -189,7 +190,7 @@ public Collection values() { @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof Container container)) return false; + if (!(o instanceof DynamicContainer container)) return false; return resource == container.resource; } diff --git a/src/main/java/net/minestom/server/utils/url/URLUtils.java b/src/main/java/net/minestom/server/utils/url/URLUtils.java index 4de19ebe9ba..a2bd3f5d1a4 100644 --- a/src/main/java/net/minestom/server/utils/url/URLUtils.java +++ b/src/main/java/net/minestom/server/utils/url/URLUtils.java @@ -1,11 +1,13 @@ package net.minestom.server.utils.url; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; -import java.net.URL; +import java.net.URI; public final class URLUtils { @@ -13,8 +15,9 @@ private URLUtils() { } + @SuppressFBWarnings("") public static String getText(String url) throws IOException { - HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); + HttpURLConnection connection = (HttpURLConnection) URI.create(url).toURL().openConnection(); //add headers to the connection, or check the status if desired.. // handle error response code it occurs diff --git a/src/main/java/net/minestom/server/world/biomes/BiomeManager.java b/src/main/java/net/minestom/server/world/biomes/BiomeManager.java index 53019a84afc..acaa1ee2f12 100644 --- a/src/main/java/net/minestom/server/world/biomes/BiomeManager.java +++ b/src/main/java/net/minestom/server/world/biomes/BiomeManager.java @@ -68,7 +68,7 @@ public void addBiome(@NotNull Biome biome) { public void removeBiome(@NotNull Biome biome) { var id = idMappings.get(biome.namespace()); if (id != null) { - biomes.remove(id); + biomes.remove(biome); biomesByName.remove(biome.namespace()); idMappings.remove(biome.namespace()); nbtCache = null;