Improved error handling for loading broken entity / tile data

This commit is contained in:
Dylan K. Taylor 2021-11-23 17:39:20 +00:00
parent eb0cf52d81
commit 5c7125f190
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
7 changed files with 92 additions and 44 deletions

View File

@ -23,8 +23,9 @@ declare(strict_types=1);
namespace pocketmine\block\tile; namespace pocketmine\block\tile;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\NbtDataException; use pocketmine\nbt\NbtException;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use pocketmine\utils\SingletonTrait; use pocketmine\utils\SingletonTrait;
use pocketmine\utils\Utils; use pocketmine\utils\Utils;
@ -112,9 +113,10 @@ final class TileFactory{
/** /**
* @internal * @internal
* @throws NbtDataException * @throws SavedDataLoadingException
*/ */
public function createFromData(World $world, CompoundTag $nbt) : ?Tile{ public function createFromData(World $world, CompoundTag $nbt) : ?Tile{
try{
$type = $nbt->getString(Tile::TAG_ID, ""); $type = $nbt->getString(Tile::TAG_ID, "");
if(!isset($this->knownTiles[$type])){ if(!isset($this->knownTiles[$type])){
return null; return null;
@ -127,6 +129,9 @@ final class TileFactory{
*/ */
$tile = new $class($world, new Vector3($nbt->getInt(Tile::TAG_X), $nbt->getInt(Tile::TAG_Y), $nbt->getInt(Tile::TAG_Z))); $tile = new $class($world, new Vector3($nbt->getInt(Tile::TAG_X), $nbt->getInt(Tile::TAG_Y), $nbt->getInt(Tile::TAG_Z)));
$tile->readSaveData($nbt); $tile->readSaveData($nbt);
}catch(NbtException $e){
throw new SavedDataLoadingException($e->getMessage(), 0, $e);
}
return $tile; return $tile;
} }

View File

@ -0,0 +1,28 @@
<?php
/*
*
* ____ _ _ __ __ _ __ __ ____
* | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
* | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
* | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
* |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* @author PocketMine Team
* @link http://www.pocketmine.net/
*
*
*/
declare(strict_types=1);
namespace pocketmine\data;
final class SavedDataLoadingException extends \RuntimeException{
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\entity; namespace pocketmine\entity;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\NBT; use pocketmine\nbt\NBT;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
@ -38,34 +39,40 @@ final class EntityDataHelper{
//NOOP //NOOP
} }
/**
* @throws SavedDataLoadingException
*/
public static function parseLocation(CompoundTag $nbt, World $world) : Location{ public static function parseLocation(CompoundTag $nbt, World $world) : Location{
$pos = self::parseVec3($nbt, "Pos", false); $pos = self::parseVec3($nbt, "Pos", false);
$yawPitch = $nbt->getTag("Rotation"); $yawPitch = $nbt->getTag("Rotation");
if(!($yawPitch instanceof ListTag) or $yawPitch->getTagType() !== NBT::TAG_Float){ if(!($yawPitch instanceof ListTag) or $yawPitch->getTagType() !== NBT::TAG_Float){
throw new \UnexpectedValueException("'Rotation' should be a List<Float>"); throw new SavedDataLoadingException("'Rotation' should be a List<Float>");
} }
/** @var FloatTag[] $values */ /** @var FloatTag[] $values */
$values = $yawPitch->getValue(); $values = $yawPitch->getValue();
if(count($values) !== 2){ if(count($values) !== 2){
throw new \UnexpectedValueException("Expected exactly 2 entries for 'Rotation'"); throw new SavedDataLoadingException("Expected exactly 2 entries for 'Rotation'");
} }
return Location::fromObject($pos, $world, $values[0]->getValue(), $values[1]->getValue()); return Location::fromObject($pos, $world, $values[0]->getValue(), $values[1]->getValue());
} }
/**
* @throws SavedDataLoadingException
*/
public static function parseVec3(CompoundTag $nbt, string $tagName, bool $optional) : Vector3{ public static function parseVec3(CompoundTag $nbt, string $tagName, bool $optional) : Vector3{
$pos = $nbt->getTag($tagName); $pos = $nbt->getTag($tagName);
if($pos === null and $optional){ if($pos === null and $optional){
return new Vector3(0, 0, 0); return new Vector3(0, 0, 0);
} }
if(!($pos instanceof ListTag) or ($pos->getTagType() !== NBT::TAG_Double && $pos->getTagType() !== NBT::TAG_Float)){ if(!($pos instanceof ListTag) or ($pos->getTagType() !== NBT::TAG_Double && $pos->getTagType() !== NBT::TAG_Float)){
throw new \UnexpectedValueException("'$tagName' should be a List<Double> or List<Float>"); throw new SavedDataLoadingException("'$tagName' should be a List<Double> or List<Float>");
} }
/** @var DoubleTag[]|FloatTag[] $values */ /** @var DoubleTag[]|FloatTag[] $values */
$values = $pos->getValue(); $values = $pos->getValue();
if(count($values) !== 3){ if(count($values) !== 3){
throw new \UnexpectedValueException("Expected exactly 3 entries in '$tagName' tag"); throw new SavedDataLoadingException("Expected exactly 3 entries in '$tagName' tag");
} }
return new Vector3($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue()); return new Vector3($values[0]->getValue(), $values[1]->getValue(), $values[2]->getValue());
} }

View File

@ -30,6 +30,7 @@ use pocketmine\block\BlockFactory;
use pocketmine\data\bedrock\EntityLegacyIds; use pocketmine\data\bedrock\EntityLegacyIds;
use pocketmine\data\bedrock\PotionTypeIdMap; use pocketmine\data\bedrock\PotionTypeIdMap;
use pocketmine\data\bedrock\PotionTypeIds; use pocketmine\data\bedrock\PotionTypeIds;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\object\ExperienceOrb; use pocketmine\entity\object\ExperienceOrb;
use pocketmine\entity\object\FallingBlock; use pocketmine\entity\object\FallingBlock;
use pocketmine\entity\object\ItemEntity; use pocketmine\entity\object\ItemEntity;
@ -45,7 +46,7 @@ use pocketmine\entity\projectile\SplashPotion;
use pocketmine\item\Item; use pocketmine\item\Item;
use pocketmine\math\Facing; use pocketmine\math\Facing;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\NbtDataException; use pocketmine\nbt\NbtException;
use pocketmine\nbt\tag\ByteTag; use pocketmine\nbt\tag\ByteTag;
use pocketmine\nbt\tag\CompoundTag; use pocketmine\nbt\tag\CompoundTag;
use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\IntTag;
@ -113,12 +114,12 @@ final class EntityFactory{
$this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{ $this->register(ItemEntity::class, function(World $world, CompoundTag $nbt) : ItemEntity{
$itemTag = $nbt->getCompoundTag("Item"); $itemTag = $nbt->getCompoundTag("Item");
if($itemTag === null){ if($itemTag === null){
throw new \UnexpectedValueException("Expected \"Item\" NBT tag not found"); throw new SavedDataLoadingException("Expected \"Item\" NBT tag not found");
} }
$item = Item::nbtDeserialize($itemTag); $item = Item::nbtDeserialize($itemTag);
if($item->isNull()){ if($item->isNull()){
throw new \UnexpectedValueException("Item is invalid"); throw new SavedDataLoadingException("Item is invalid");
} }
return new ItemEntity(EntityDataHelper::parseLocation($nbt, $world), $item, $nbt); return new ItemEntity(EntityDataHelper::parseLocation($nbt, $world), $item, $nbt);
}, ['Item', 'minecraft:item'], EntityLegacyIds::ITEM); }, ['Item', 'minecraft:item'], EntityLegacyIds::ITEM);
@ -126,7 +127,7 @@ final class EntityFactory{
$this->register(Painting::class, function(World $world, CompoundTag $nbt) : Painting{ $this->register(Painting::class, function(World $world, CompoundTag $nbt) : Painting{
$motive = PaintingMotive::getMotiveByName($nbt->getString("Motive")); $motive = PaintingMotive::getMotiveByName($nbt->getString("Motive"));
if($motive === null){ if($motive === null){
throw new \UnexpectedValueException("Unknown painting motive"); throw new SavedDataLoadingException("Unknown painting motive");
} }
$blockIn = new Vector3($nbt->getInt("TileX"), $nbt->getInt("TileY"), $nbt->getInt("TileZ")); $blockIn = new Vector3($nbt->getInt("TileX"), $nbt->getInt("TileY"), $nbt->getInt("TileZ"));
if(($directionTag = $nbt->getTag("Direction")) instanceof ByteTag){ if(($directionTag = $nbt->getTag("Direction")) instanceof ByteTag){
@ -134,7 +135,7 @@ final class EntityFactory{
}elseif(($facingTag = $nbt->getTag("Facing")) instanceof ByteTag){ }elseif(($facingTag = $nbt->getTag("Facing")) instanceof ByteTag){
$facing = Painting::DATA_TO_FACING[$facingTag->getValue()] ?? Facing::NORTH; $facing = Painting::DATA_TO_FACING[$facingTag->getValue()] ?? Facing::NORTH;
}else{ }else{
throw new \UnexpectedValueException("Missing facing info"); throw new SavedDataLoadingException("Missing facing info");
} }
return new Painting(EntityDataHelper::parseLocation($nbt, $world), $blockIn, $facing, $motive, $nbt); return new Painting(EntityDataHelper::parseLocation($nbt, $world), $blockIn, $facing, $motive, $nbt);
@ -151,7 +152,7 @@ final class EntityFactory{
$this->register(SplashPotion::class, function(World $world, CompoundTag $nbt) : SplashPotion{ $this->register(SplashPotion::class, function(World $world, CompoundTag $nbt) : SplashPotion{
$potionType = PotionTypeIdMap::getInstance()->fromId($nbt->getShort("PotionId", PotionTypeIds::WATER)); $potionType = PotionTypeIdMap::getInstance()->fromId($nbt->getShort("PotionId", PotionTypeIds::WATER));
if($potionType === null){ if($potionType === null){
throw new \UnexpectedValueException("No such potion type"); throw new SavedDataLoadingException("No such potion type");
} }
return new SplashPotion(EntityDataHelper::parseLocation($nbt, $world), null, $potionType, $nbt); return new SplashPotion(EntityDataHelper::parseLocation($nbt, $world), null, $potionType, $nbt);
}, ['ThrownPotion', 'minecraft:potion', 'thrownpotion'], EntityLegacyIds::SPLASH_POTION); }, ['ThrownPotion', 'minecraft:potion', 'thrownpotion'], EntityLegacyIds::SPLASH_POTION);
@ -222,11 +223,11 @@ final class EntityFactory{
/** /**
* Creates an entity from data stored on a chunk. * Creates an entity from data stored on a chunk.
* *
* @throws \RuntimeException * @throws SavedDataLoadingException
* @throws NbtDataException
* @internal * @internal
*/ */
public function createFromData(World $world, CompoundTag $nbt) : ?Entity{ public function createFromData(World $world, CompoundTag $nbt) : ?Entity{
try{
$saveId = $nbt->getTag("id") ?? $nbt->getTag("identifier"); $saveId = $nbt->getTag("id") ?? $nbt->getTag("identifier");
$func = null; $func = null;
if($saveId instanceof StringTag){ if($saveId instanceof StringTag){
@ -241,6 +242,9 @@ final class EntityFactory{
$entity = $func($world, $nbt); $entity = $func($world, $nbt);
return $entity; return $entity;
}catch(NbtException $e){
throw new SavedDataLoadingException($e->getMessage(), 0, $e);
}
} }
public function injectSaveId(string $class, CompoundTag $saveData) : void{ public function injectSaveId(string $class, CompoundTag $saveData) : void{

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\entity; namespace pocketmine\entity;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\animation\TotemUseAnimation; use pocketmine\entity\animation\TotemUseAnimation;
use pocketmine\entity\effect\EffectInstance; use pocketmine\entity\effect\EffectInstance;
use pocketmine\entity\effect\VanillaEffects; use pocketmine\entity\effect\VanillaEffects;
@ -104,12 +105,12 @@ class Human extends Living implements ProjectileSource, InventoryHolder{
/** /**
* @throws InvalidSkinException * @throws InvalidSkinException
* @throws \UnexpectedValueException * @throws SavedDataLoadingException
*/ */
public static function parseSkinNBT(CompoundTag $nbt) : Skin{ public static function parseSkinNBT(CompoundTag $nbt) : Skin{
$skinTag = $nbt->getCompoundTag("Skin"); $skinTag = $nbt->getCompoundTag("Skin");
if($skinTag === null){ if($skinTag === null){
throw new \UnexpectedValueException("Missing skin data"); throw new SavedDataLoadingException("Missing skin data");
} }
return new Skin( //this throws if the skin is invalid return new Skin( //this throws if the skin is invalid
$skinTag->getString("Name"), $skinTag->getString("Name"),

View File

@ -31,6 +31,7 @@ use pocketmine\block\BlockBreakInfo;
use pocketmine\block\BlockToolType; use pocketmine\block\BlockToolType;
use pocketmine\block\VanillaBlocks; use pocketmine\block\VanillaBlocks;
use pocketmine\data\bedrock\EnchantmentIdMap; use pocketmine\data\bedrock\EnchantmentIdMap;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\Entity; use pocketmine\entity\Entity;
use pocketmine\item\enchantment\EnchantmentInstance; use pocketmine\item\enchantment\EnchantmentInstance;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
@ -671,6 +672,8 @@ class Item implements \JsonSerializable{
/** /**
* Deserializes an Item from an NBT CompoundTag * Deserializes an Item from an NBT CompoundTag
* @throws NbtException
* @throws SavedDataLoadingException
*/ */
public static function nbtDeserialize(CompoundTag $tag) : Item{ public static function nbtDeserialize(CompoundTag $tag) : Item{
if($tag->getTag("id") === null or $tag->getTag("Count") === null){ if($tag->getTag("id") === null or $tag->getTag("Count") === null){
@ -692,7 +695,7 @@ class Item implements \JsonSerializable{
} }
$item->setCount($count); $item->setCount($count);
}else{ }else{
throw new \InvalidArgumentException("Item CompoundTag ID must be an instance of StringTag or ShortTag, " . get_class($idTag) . " given"); throw new SavedDataLoadingException("Item CompoundTag ID must be an instance of StringTag or ShortTag, " . get_class($idTag) . " given");
} }
$itemNBT = $tag->getCompoundTag("tag"); $itemNBT = $tag->getCompoundTag("tag");

View File

@ -36,6 +36,7 @@ use pocketmine\block\tile\TileFactory;
use pocketmine\block\UnknownBlock; use pocketmine\block\UnknownBlock;
use pocketmine\block\VanillaBlocks; use pocketmine\block\VanillaBlocks;
use pocketmine\data\bedrock\BiomeIds; use pocketmine\data\bedrock\BiomeIds;
use pocketmine\data\SavedDataLoadingException;
use pocketmine\entity\Entity; use pocketmine\entity\Entity;
use pocketmine\entity\EntityFactory; use pocketmine\entity\EntityFactory;
use pocketmine\entity\Location; use pocketmine\entity\Location;
@ -57,7 +58,6 @@ use pocketmine\item\LegacyStringToItemParser;
use pocketmine\lang\KnownTranslationFactory; use pocketmine\lang\KnownTranslationFactory;
use pocketmine\math\AxisAlignedBB; use pocketmine\math\AxisAlignedBB;
use pocketmine\math\Vector3; use pocketmine\math\Vector3;
use pocketmine\nbt\NbtDataException;
use pocketmine\nbt\tag\IntTag; use pocketmine\nbt\tag\IntTag;
use pocketmine\nbt\tag\StringTag; use pocketmine\nbt\tag\StringTag;
use pocketmine\network\mcpe\convert\RuntimeBlockMapping; use pocketmine\network\mcpe\convert\RuntimeBlockMapping;
@ -2472,7 +2472,7 @@ class World implements ChunkManager{
foreach($chunkData->getEntityNBT() as $k => $nbt){ foreach($chunkData->getEntityNBT() as $k => $nbt){
try{ try{
$entity = $entityFactory->createFromData($this, $nbt); $entity = $entityFactory->createFromData($this, $nbt);
}catch(NbtDataException $e){ }catch(SavedDataLoadingException $e){
$logger->error("Bad entity data at list position $k: " . $e->getMessage()); $logger->error("Bad entity data at list position $k: " . $e->getMessage());
$logger->logException($e); $logger->logException($e);
continue; continue;
@ -2500,7 +2500,7 @@ class World implements ChunkManager{
foreach($chunkData->getTileNBT() as $k => $nbt){ foreach($chunkData->getTileNBT() as $k => $nbt){
try{ try{
$tile = $tileFactory->createFromData($this, $nbt); $tile = $tileFactory->createFromData($this, $nbt);
}catch(NbtDataException $e){ }catch(SavedDataLoadingException $e){
$logger->error("Bad tile entity data at list position $k: " . $e->getMessage()); $logger->error("Bad tile entity data at list position $k: " . $e->getMessage());
$logger->logException($e); $logger->logException($e);
continue; continue;