diff --git a/src/pocketmine/CrashDump.php b/src/pocketmine/CrashDump.php index 8cefb4912..353a423fe 100644 --- a/src/pocketmine/CrashDump.php +++ b/src/pocketmine/CrashDump.php @@ -87,7 +87,11 @@ class CrashDump{ * having their content changed, version format changing, etc. * It is not necessary to increase this when adding new fields. */ - private const FORMAT_VERSION = 1; + private const FORMAT_VERSION = 2; + + private const PLUGIN_INVOLVEMENT_NONE = "none"; + private const PLUGIN_INVOLVEMENT_DIRECT = "direct"; + private const PLUGIN_INVOLVEMENT_INDIRECT = "indirect"; /** @var Server */ private $server; @@ -207,7 +211,7 @@ class CrashDump{ $error = $lastExceptionError; }else{ $error = (array) error_get_last(); - $error["trace"] = Utils::printableCurrentTrace(3); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump + $error["trace"] = Utils::currentTrace(3); //Skipping CrashDump->baseCrash, CrashDump->construct, Server->crashDump $errorConversion = [ E_ERROR => "E_ERROR", E_WARNING => "E_WARNING", @@ -245,24 +249,16 @@ class CrashDump{ $this->addLine("Line: " . $error["line"]); $this->addLine("Type: " . $error["type"]); - if(strpos($error["file"], "src/pocketmine/") === false and strpos($error["file"], "vendor/pocketmine/") === false and file_exists($error["fullFile"])){ - $this->addLine(); - $this->addLine("THIS CRASH WAS CAUSED BY A PLUGIN"); - $this->data["plugin"] = true; - - $reflection = new \ReflectionClass(PluginBase::class); - $file = $reflection->getProperty("file"); - $file->setAccessible(true); - foreach($this->server->getPluginManager()->getPlugins() as $plugin){ - $filePath = Utils::cleanPath($file->getValue($plugin)); - if(strpos($error["file"], $filePath) === 0){ - $this->data["plugin"] = $plugin->getName(); - $this->addLine("BAD PLUGIN: " . $plugin->getDescription()->getFullName()); + $this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_NONE; + if(!$this->determinePluginFromFile($error["fullFile"], true)){ //fatal errors won't leave any stack trace + foreach($error["trace"] as $frame){ + if(!isset($frame["file"])){ + continue; //PHP core + } + if($this->determinePluginFromFile($frame["file"], false)){ break; } } - }else{ - $this->data["plugin"] = false; } $this->addLine(); @@ -279,12 +275,40 @@ class CrashDump{ $this->addLine(); $this->addLine("Backtrace:"); - foreach(($this->data["trace"] = $error["trace"]) as $line){ + foreach(($this->data["trace"] = Utils::printableTrace($error["trace"])) as $line){ $this->addLine($line); } $this->addLine(); } + private function determinePluginFromFile(string $filePath, bool $crashFrame) : bool{ + $frameCleanPath = Utils::cleanPath($filePath); //this will be empty in phar stub + if($frameCleanPath !== "" and strpos($frameCleanPath, "src/pocketmine/") === false and strpos($frameCleanPath, "vendor/pocketmine/") === false and file_exists($filePath)){ + $this->addLine(); + if($crashFrame){ + $this->addLine("THIS CRASH WAS CAUSED BY A PLUGIN"); + $this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_DIRECT; + }else{ + $this->addLine("A PLUGIN WAS INVOLVED IN THIS CRASH"); + $this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_INDIRECT; + } + + $reflection = new \ReflectionClass(PluginBase::class); + $file = $reflection->getProperty("file"); + $file->setAccessible(true); + foreach($this->server->getPluginManager()->getPlugins() as $plugin){ + $filePath = Utils::cleanPath($file->getValue($plugin)); + if(strpos($frameCleanPath, $filePath) === 0){ + $this->data["plugin"] = $plugin->getName(); + $this->addLine("BAD PLUGIN: " . $plugin->getDescription()->getFullName()); + break; + } + } + return true; + } + return false; + } + private function generalData(){ $version = new VersionString(\pocketmine\BASE_VERSION, \pocketmine\IS_DEVELOPMENT_BUILD, \pocketmine\BUILD_NUMBER); $this->data["general"] = []; diff --git a/src/pocketmine/Server.php b/src/pocketmine/Server.php index 0530d2fbb..ece48518e 100644 --- a/src/pocketmine/Server.php +++ b/src/pocketmine/Server.php @@ -104,7 +104,6 @@ use pocketmine\tile\Tile; use pocketmine\timings\Timings; use pocketmine\timings\TimingsHandler; use pocketmine\updater\AutoUpdater; -use pocketmine\utils\Binary; use pocketmine\utils\Config; use pocketmine\utils\Internet; use pocketmine\utils\MainLogger; @@ -151,6 +150,7 @@ use function pcntl_signal; use function pcntl_signal_dispatch; use function preg_replace; use function random_bytes; +use function random_int; use function realpath; use function register_shutdown_function; use function rename; @@ -169,6 +169,8 @@ use function time; use function touch; use function trim; use const DIRECTORY_SEPARATOR; +use const INT32_MAX; +use const INT32_MIN; use const PHP_EOL; use const PHP_INT_MAX; use const PTHREADS_INHERIT_NONE; @@ -1120,7 +1122,7 @@ class Server{ return false; } - $seed = $seed ?? Binary::readInt(random_bytes(4)); + $seed = $seed ?? random_int(INT32_MIN, INT32_MAX); if(!isset($options["preset"])){ $options["preset"] = $this->getConfigString("generator-settings", ""); @@ -2273,7 +2275,7 @@ class Server{ "fullFile" => $e->getFile(), "file" => $errfile, "line" => $errline, - "trace" => Utils::printableTrace($trace) + "trace" => $trace ]; global $lastExceptionError, $lastError; @@ -2312,7 +2314,7 @@ class Server{ if(is_string($plugin)){ $p = $this->pluginManager->getPlugin($plugin); if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){ - $report = false; + $this->logger->debug("Not sending crashdump due to caused by non-phar plugin"); } } diff --git a/src/pocketmine/utils/Utils.php b/src/pocketmine/utils/Utils.php index d99c21f6f..0780126c8 100644 --- a/src/pocketmine/utils/Utils.php +++ b/src/pocketmine/utils/Utils.php @@ -578,7 +578,21 @@ class Utils{ } public static function cleanPath($path){ - return str_replace(["\\", ".php", "phar://", str_replace(["\\", "phar://"], ["/", ""], \pocketmine\PATH), str_replace(["\\", "phar://"], ["/", ""], \pocketmine\PLUGIN_PATH)], ["/", "", "", "", ""], $path); + $result = str_replace(["\\", ".php", "phar://"], ["/", "", ""], $path); + + //remove relative paths + //TODO: make these paths dynamic so they can be unit-tested against + static $cleanPaths = [ + \pocketmine\PLUGIN_PATH => "plugins", //this has to come BEFORE \pocketmine\PATH because it's inside that by default on src installations + \pocketmine\PATH => "" + ]; + foreach($cleanPaths as $cleanPath => $replacement){ + $cleanPath = rtrim(str_replace(["\\", "phar://"], ["/", ""], $cleanPath), "/"); + if(strpos($result, $cleanPath) === 0){ + $result = ltrim(str_replace($cleanPath, $replacement, $result), "/"); + } + } + return $result; } /**