From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sun, 12 May 2024 10:42:42 -0700 Subject: [PATCH] Improve Recipe validation diff --git a/src/main/java/org/bukkit/inventory/CookingRecipe.java b/src/main/java/org/bukkit/inventory/CookingRecipe.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/inventory/CookingRecipe.java +++ b/src/main/java/org/bukkit/inventory/CookingRecipe.java @@ -0,0 +0,0 @@ public abstract class CookingRecipe implements Recipe, * @param cookingTime The cooking time (in ticks) */ public CookingRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result, @NotNull RecipeChoice input, float experience, int cookingTime) { - Preconditions.checkArgument(result.getType() != Material.AIR, "Recipe must have non-AIR result."); + Preconditions.checkArgument(!result.isEmpty(), "Recipe cannot have an empty result."); // Paper this.key = key; this.output = new ItemStack(result); - this.ingredient = input; + this.ingredient = input.validate().clone(); // Paper this.experience = experience; this.cookingTime = cookingTime; } @@ -0,0 +0,0 @@ public abstract class CookingRecipe implements Recipe, */ @NotNull public T setInputChoice(@NotNull RecipeChoice input) { - this.ingredient = input; + this.ingredient = input.validate().clone(); // Paper return (T) this; } diff --git a/src/main/java/org/bukkit/inventory/CraftingRecipe.java b/src/main/java/org/bukkit/inventory/CraftingRecipe.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/inventory/CraftingRecipe.java +++ b/src/main/java/org/bukkit/inventory/CraftingRecipe.java @@ -0,0 +0,0 @@ public abstract class CraftingRecipe implements Recipe, Keyed { protected CraftingRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result) { Preconditions.checkArgument(key != null, "key cannot be null"); - Preconditions.checkArgument(result.getType() != Material.AIR, "Recipe must have non-AIR result."); + Preconditions.checkArgument(!result.isEmpty(), "Recipe cannot have an empty result."); // Paper this.key = key; this.output = new ItemStack(result); } diff --git a/src/main/java/org/bukkit/inventory/MerchantRecipe.java b/src/main/java/org/bukkit/inventory/MerchantRecipe.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/inventory/MerchantRecipe.java +++ b/src/main/java/org/bukkit/inventory/MerchantRecipe.java @@ -0,0 +0,0 @@ public class MerchantRecipe implements Recipe { this(result, uses, maxUses, experienceReward, villagerExperience, priceMultiplier, 0, 0, ignoreDiscounts); } public MerchantRecipe(@NotNull ItemStack result, int uses, int maxUses, boolean experienceReward, int villagerExperience, float priceMultiplier, int demand, int specialPrice, boolean ignoreDiscounts) { + Preconditions.checkArgument(!result.isEmpty(), "Recipe cannot have an empty result."); // Paper this.ignoreDiscounts = ignoreDiscounts; // Paper end this.result = result; @@ -0,0 +0,0 @@ public class MerchantRecipe implements Recipe { @NotNull @Override public ItemStack getResult() { - return result; + return result.clone(); // Paper } public void addIngredient(@NotNull ItemStack item) { Preconditions.checkState(ingredients.size() < 2, "MerchantRecipe can only have maximum 2 ingredients"); + Preconditions.checkArgument(!item.isEmpty(), "Recipe cannot have an empty itemstack ingredient."); // Paper ingredients.add(item.clone()); } @@ -0,0 +0,0 @@ public class MerchantRecipe implements Recipe { Preconditions.checkState(ingredients.size() <= 2, "MerchantRecipe can only have maximum 2 ingredients"); this.ingredients = new ArrayList(); for (ItemStack item : ingredients) { + Preconditions.checkArgument(!item.isEmpty(), "Recipe cannot have an empty itemstack ingredient."); // Paper this.ingredients.add(item.clone()); } } diff --git a/src/main/java/org/bukkit/inventory/RecipeChoice.java b/src/main/java/org/bukkit/inventory/RecipeChoice.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/inventory/RecipeChoice.java +++ b/src/main/java/org/bukkit/inventory/RecipeChoice.java @@ -0,0 +0,0 @@ public interface RecipeChoice extends Predicate, Cloneable { @Override boolean test(@NotNull ItemStack itemStack); + // Paper start - check valid ingredients + @org.jetbrains.annotations.ApiStatus.Internal + default @NotNull RecipeChoice validate() { + return this; + } + // Paper end - check valid ingredients + /** * Represents a choice of multiple matching Materials. */ @@ -0,0 +0,0 @@ public interface RecipeChoice extends Predicate, Cloneable { public ExactChoice clone() { try { ExactChoice clone = (ExactChoice) super.clone(); - clone.choices = new ArrayList<>(choices); + // Paper start - properly clone + clone.choices = new ArrayList<>(this.choices.size()); + for (ItemStack choice : this.choices) { + clone.choices.add(choice.clone()); + } + // Paper end - properly clone return clone; } catch (CloneNotSupportedException ex) { throw new AssertionError(ex); @@ -0,0 +0,0 @@ public interface RecipeChoice extends Predicate, Cloneable { public String toString() { return "ExactChoice{" + "choices=" + choices + '}'; } + + // Paper start - check valid ingredients + @Override + public @NotNull RecipeChoice validate() { + if (this.choices.stream().anyMatch(s -> s.getType().isAir())) { + throw new IllegalArgumentException("RecipeChoice.ExactChoice cannot contain air"); + } + return this; + } + // Paper end - check valid ingredients } } diff --git a/src/main/java/org/bukkit/inventory/ShapedRecipe.java b/src/main/java/org/bukkit/inventory/ShapedRecipe.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/inventory/ShapedRecipe.java +++ b/src/main/java/org/bukkit/inventory/ShapedRecipe.java @@ -0,0 +0,0 @@ public class ShapedRecipe extends CraftingRecipe { public ShapedRecipe setIngredient(char key, @NotNull RecipeChoice ingredient) { Preconditions.checkArgument(ingredients.containsKey(key), "Symbol does not appear in the shape:", key); - ingredients.put(key, ingredient); + ingredients.put(key, ingredient.validate().clone()); // Paper return this; } // Paper start @NotNull public ShapedRecipe setIngredient(char key, @NotNull ItemStack item) { - return setIngredient(key, new RecipeChoice.ExactChoice(item)); + Preconditions.checkArgument(!item.getType().isAir(), "Item cannot be air"); // Paper + return setIngredient(key, new RecipeChoice.ExactChoice(item.clone())); // Paper } // Paper end diff --git a/src/main/java/org/bukkit/inventory/ShapelessRecipe.java b/src/main/java/org/bukkit/inventory/ShapelessRecipe.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/inventory/ShapelessRecipe.java +++ b/src/main/java/org/bukkit/inventory/ShapelessRecipe.java @@ -0,0 +0,0 @@ public class ShapelessRecipe extends CraftingRecipe { public ShapelessRecipe addIngredient(@NotNull RecipeChoice ingredient) { Preconditions.checkArgument(ingredients.size() + 1 <= 9, "Shapeless recipes cannot have more than 9 ingredients"); - ingredients.add(ingredient); + ingredients.add(ingredient.validate().clone()); // Paper return this; } @@ -0,0 +0,0 @@ public class ShapelessRecipe extends CraftingRecipe { @NotNull public ShapelessRecipe addIngredient(int count, @NotNull ItemStack item) { Preconditions.checkArgument(ingredients.size() + count <= 9, "Shapeless recipes cannot have more than 9 ingredients"); + Preconditions.checkArgument(!item.getType().isAir(), "Item cannot be air"); // Paper + item = item.clone(); // Paper while (count-- > 0) { ingredients.add(new RecipeChoice.ExactChoice(item)); } diff --git a/src/main/java/org/bukkit/inventory/SmithingRecipe.java b/src/main/java/org/bukkit/inventory/SmithingRecipe.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/inventory/SmithingRecipe.java +++ b/src/main/java/org/bukkit/inventory/SmithingRecipe.java @@ -0,0 +0,0 @@ public class SmithingRecipe implements Recipe, Keyed { */ @Deprecated public SmithingRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result, @NotNull RecipeChoice base, @NotNull RecipeChoice addition, boolean copyDataComponents) { + com.google.common.base.Preconditions.checkArgument(!result.isEmpty() || this instanceof ComplexRecipe, "Recipe cannot have an empty result."); // Paper this.copyDataComponents = copyDataComponents; // Paper end this.key = key; this.result = result; - this.base = base; - this.addition = addition; + this.base = base.validate().clone(); // Paper + this.addition = addition.validate().clone(); // Paper } /** diff --git a/src/main/java/org/bukkit/inventory/SmithingTransformRecipe.java b/src/main/java/org/bukkit/inventory/SmithingTransformRecipe.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/inventory/SmithingTransformRecipe.java +++ b/src/main/java/org/bukkit/inventory/SmithingTransformRecipe.java @@ -0,0 +0,0 @@ public class SmithingTransformRecipe extends SmithingRecipe { */ public SmithingTransformRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result, @NotNull RecipeChoice template, @NotNull RecipeChoice base, @NotNull RecipeChoice addition) { super(key, result, base, addition); - this.template = template; + this.template = template.validate().clone(); // Paper } // Paper start /** diff --git a/src/main/java/org/bukkit/inventory/SmithingTrimRecipe.java b/src/main/java/org/bukkit/inventory/SmithingTrimRecipe.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/inventory/SmithingTrimRecipe.java +++ b/src/main/java/org/bukkit/inventory/SmithingTrimRecipe.java @@ -0,0 +0,0 @@ public class SmithingTrimRecipe extends SmithingRecipe implements ComplexRecipe */ public SmithingTrimRecipe(@NotNull NamespacedKey key, @NotNull RecipeChoice template, @NotNull RecipeChoice base, @NotNull RecipeChoice addition) { super(key, new ItemStack(Material.AIR), base, addition); - this.template = template; + this.template = template.validate().clone(); // Paper } // Paper start /** @@ -0,0 +0,0 @@ public class SmithingTrimRecipe extends SmithingRecipe implements ComplexRecipe */ public SmithingTrimRecipe(@NotNull NamespacedKey key, @NotNull RecipeChoice template, @NotNull RecipeChoice base, @NotNull RecipeChoice addition, boolean copyDataComponents) { super(key, new ItemStack(Material.AIR), base, addition, copyDataComponents); - this.template = template; + this.template = template.validate().clone(); // Paper } // Paper end diff --git a/src/main/java/org/bukkit/inventory/StonecuttingRecipe.java b/src/main/java/org/bukkit/inventory/StonecuttingRecipe.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/inventory/StonecuttingRecipe.java +++ b/src/main/java/org/bukkit/inventory/StonecuttingRecipe.java @@ -0,0 +0,0 @@ public class StonecuttingRecipe implements Recipe, Keyed { * @param input The input choices. */ public StonecuttingRecipe(@NotNull NamespacedKey key, @NotNull ItemStack result, @NotNull RecipeChoice input) { - Preconditions.checkArgument(result.getType() != Material.AIR, "Recipe must have non-AIR result."); + Preconditions.checkArgument(!result.isEmpty(), "Recipe cannot have an empty result."); // Paper this.key = key; this.output = new ItemStack(result); - this.ingredient = input; + this.ingredient = input.validate().clone(); // Paper } /** @@ -0,0 +0,0 @@ public class StonecuttingRecipe implements Recipe, Keyed { */ @NotNull public StonecuttingRecipe setInputChoice(@NotNull RecipeChoice input) { - this.ingredient = input; + this.ingredient = input.validate().clone(); // Paper return (StonecuttingRecipe) this; }