Use object models for crashdump generation

This commit is contained in:
Dylan K. Taylor 2021-11-04 16:55:04 +00:00
parent 4f8501ff34
commit 8ac999cbd4
No known key found for this signature in database
GPG Key ID: 8927471A91CAFD3D
5 changed files with 233 additions and 68 deletions

View File

@ -24,6 +24,9 @@ declare(strict_types=1);
namespace pocketmine; namespace pocketmine;
use Composer\InstalledVersions; use Composer\InstalledVersions;
use pocketmine\crash\CrashDumpData;
use pocketmine\crash\CrashDumpDataGeneral;
use pocketmine\crash\CrashDumpDataPluginEntry;
use pocketmine\errorhandler\ErrorTypeToStringMap; use pocketmine\errorhandler\ErrorTypeToStringMap;
use pocketmine\network\mcpe\protocol\ProtocolInfo; use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\plugin\PluginBase; use pocketmine\plugin\PluginBase;
@ -69,6 +72,7 @@ use const FILE_IGNORE_NEW_LINES;
use const JSON_UNESCAPED_SLASHES; use const JSON_UNESCAPED_SLASHES;
use const PHP_EOL; use const PHP_EOL;
use const PHP_OS; use const PHP_OS;
use const PHP_VERSION;
use const SORT_STRING; use const SORT_STRING;
class CrashDump{ class CrashDump{
@ -91,13 +95,9 @@ class CrashDump{
private $fp; private $fp;
/** @var float */ /** @var float */
private $time; private $time;
/** private CrashDumpData $data;
* @var mixed[]
* @phpstan-var array<string, mixed>
*/
private $data = [];
/** @var string */ /** @var string */
private $encodedData = ""; private $encodedData;
/** @var string */ /** @var string */
private $path; private $path;
@ -118,9 +118,10 @@ class CrashDump{
throw new \RuntimeException("Could not create Crash Dump"); throw new \RuntimeException("Could not create Crash Dump");
} }
$this->fp = $fp; $this->fp = $fp;
$this->data["format_version"] = self::FORMAT_VERSION; $this->data = new CrashDumpData();
$this->data["time"] = $this->time; $this->data->format_version = self::FORMAT_VERSION;
$this->data["uptime"] = $this->time - $this->server->getStartTime(); $this->data->time = $this->time;
$this->data->uptime = $this->time - $this->server->getStartTime();
$this->addLine($this->server->getName() . " Crash Dump " . date("D M j H:i:s T Y", (int) $this->time)); $this->addLine($this->server->getName() . " Crash Dump " . date("D M j H:i:s T Y", (int) $this->time));
$this->addLine(); $this->addLine();
$this->baseCrash(); $this->baseCrash();
@ -142,11 +143,7 @@ class CrashDump{
return $this->encodedData; return $this->encodedData;
} }
/** public function getData() : CrashDumpData{
* @return mixed[]
* @phpstan-return array<string, mixed>
*/
public function getData() : array{
return $this->data; return $this->data;
} }
@ -173,22 +170,21 @@ class CrashDump{
$plugins = $this->pluginManager->getPlugins(); $plugins = $this->pluginManager->getPlugins();
$this->addLine(); $this->addLine();
$this->addLine("Loaded plugins:"); $this->addLine("Loaded plugins:");
$this->data["plugins"] = [];
ksort($plugins, SORT_STRING); ksort($plugins, SORT_STRING);
foreach($plugins as $p){ foreach($plugins as $p){
$d = $p->getDescription(); $d = $p->getDescription();
$this->data["plugins"][$d->getName()] = [ $this->data->plugins[$d->getName()] = new CrashDumpDataPluginEntry(
"name" => $d->getName(), name: $d->getName(),
"version" => $d->getVersion(), version: $d->getVersion(),
"authors" => $d->getAuthors(), authors: $d->getAuthors(),
"api" => $d->getCompatibleApis(), api: $d->getCompatibleApis(),
"enabled" => $p->isEnabled(), enabled: $p->isEnabled(),
"depends" => $d->getDepend(), depends: $d->getDepend(),
"softDepends" => $d->getSoftDepend(), softDepends: $d->getSoftDepend(),
"main" => $d->getMain(), main: $d->getMain(),
"load" => mb_strtoupper($d->getOrder()->name()), load: mb_strtoupper($d->getOrder()->name()),
"website" => $d->getWebsite() website: $d->getWebsite()
]; );
$this->addLine($d->getName() . " " . $d->getVersion() . " by " . implode(", ", $d->getAuthors()) . " for API(s) " . implode(", ", $d->getCompatibleApis())); $this->addLine($d->getName() . " " . $d->getVersion() . " by " . implode(", ", $d->getAuthors()) . " for API(s) " . implode(", ", $d->getCompatibleApis()));
} }
} }
@ -198,32 +194,26 @@ class CrashDump{
global $argv; global $argv;
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-settings", true)){ if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-settings", true)){
$this->data["parameters"] = (array) $argv; $this->data->parameters = (array) $argv;
if(($serverDotProperties = @file_get_contents(Path::join($this->server->getDataPath(), "server.properties"))) !== false){ if(($serverDotProperties = @file_get_contents(Path::join($this->server->getDataPath(), "server.properties"))) !== false){
$this->data["server.properties"] = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $serverDotProperties); $this->data->serverDotProperties = preg_replace("#^rcon\\.password=(.*)$#m", "rcon.password=******", $serverDotProperties) ?? throw new AssumptionFailedError("Pattern is valid");
}else{
$this->data["server.properties"] = $serverDotProperties;
} }
if(($pocketmineDotYml = @file_get_contents(Path::join($this->server->getDataPath(), "pocketmine.yml"))) !== false){ if(($pocketmineDotYml = @file_get_contents(Path::join($this->server->getDataPath(), "pocketmine.yml"))) !== false){
$this->data["pocketmine.yml"] = $pocketmineDotYml; $this->data->pocketmineDotYml = $pocketmineDotYml;
}else{
$this->data["pocketmine.yml"] = "";
} }
}else{
$this->data["pocketmine.yml"] = "";
$this->data["server.properties"] = "";
$this->data["parameters"] = [];
} }
$extensions = []; $extensions = [];
foreach(get_loaded_extensions() as $ext){ foreach(get_loaded_extensions() as $ext){
$extensions[$ext] = phpversion($ext); $version = phpversion($ext);
if($version === false) throw new AssumptionFailedError();
$extensions[$ext] = $version;
} }
$this->data["extensions"] = $extensions; $this->data->extensions = $extensions;
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-phpinfo", true)){ if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-phpinfo", true)){
ob_start(); ob_start();
phpinfo(); phpinfo();
$this->data["phpinfo"] = ob_get_contents(); $this->data->phpinfo = ob_get_contents(); // @phpstan-ignore-line
ob_end_clean(); ob_end_clean();
} }
} }
@ -255,18 +245,18 @@ class CrashDump{
if(isset($lastError["trace"])){ if(isset($lastError["trace"])){
$lastError["trace"] = Utils::printableTrace($lastError["trace"]); $lastError["trace"] = Utils::printableTrace($lastError["trace"]);
} }
$this->data["lastError"] = $lastError; $this->data->lastError = $lastError;
} }
$this->data["error"] = $error; $this->data->error = $error;
unset($this->data["error"]["fullFile"]); unset($this->data->error["fullFile"]);
unset($this->data["error"]["trace"]); unset($this->data->error["trace"]);
$this->addLine("Error: " . $error["message"]); $this->addLine("Error: " . $error["message"]);
$this->addLine("File: " . $error["file"]); $this->addLine("File: " . $error["file"]);
$this->addLine("Line: " . $error["line"]); $this->addLine("Line: " . $error["line"]);
$this->addLine("Type: " . $error["type"]); $this->addLine("Type: " . $error["type"]);
$this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_NONE; $this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_NONE;
if(!$this->determinePluginFromFile($error["fullFile"], true)){ //fatal errors won't leave any stack trace if(!$this->determinePluginFromFile($error["fullFile"], true)){ //fatal errors won't leave any stack trace
foreach($error["trace"] as $frame){ foreach($error["trace"] as $frame){
if(!isset($frame["file"])){ if(!isset($frame["file"])){
@ -280,21 +270,20 @@ class CrashDump{
$this->addLine(); $this->addLine();
$this->addLine("Code:"); $this->addLine("Code:");
$this->data["code"] = [];
if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-code", true) and file_exists($error["fullFile"])){ if($this->server->getConfigGroup()->getPropertyBool("auto-report.send-code", true) and file_exists($error["fullFile"])){
$file = @file($error["fullFile"], FILE_IGNORE_NEW_LINES); $file = @file($error["fullFile"], FILE_IGNORE_NEW_LINES);
if($file !== false){ if($file !== false){
for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10 and isset($file[$l]); ++$l){ for($l = max(0, $error["line"] - 10); $l < $error["line"] + 10 and isset($file[$l]); ++$l){
$this->addLine("[" . ($l + 1) . "] " . $file[$l]); $this->addLine("[" . ($l + 1) . "] " . $file[$l]);
$this->data["code"][$l + 1] = $file[$l]; $this->data->code[$l + 1] = $file[$l];
} }
} }
} }
$this->addLine(); $this->addLine();
$this->addLine("Backtrace:"); $this->addLine("Backtrace:");
foreach(($this->data["trace"] = Utils::printableTrace($error["trace"])) as $line){ foreach(($this->data->trace = Utils::printableTrace($error["trace"])) as $line){
$this->addLine($line); $this->addLine($line);
} }
$this->addLine(); $this->addLine();
@ -306,10 +295,10 @@ class CrashDump{
$this->addLine(); $this->addLine();
if($crashFrame){ if($crashFrame){
$this->addLine("THIS CRASH WAS CAUSED BY A PLUGIN"); $this->addLine("THIS CRASH WAS CAUSED BY A PLUGIN");
$this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_DIRECT; $this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_DIRECT;
}else{ }else{
$this->addLine("A PLUGIN WAS INVOLVED IN THIS CRASH"); $this->addLine("A PLUGIN WAS INVOLVED IN THIS CRASH");
$this->data["plugin_involvement"] = self::PLUGIN_INVOLVEMENT_INDIRECT; $this->data->plugin_involvement = self::PLUGIN_INVOLVEMENT_INDIRECT;
} }
if(file_exists($filePath)){ if(file_exists($filePath)){
@ -319,7 +308,7 @@ class CrashDump{
foreach($this->server->getPluginManager()->getPlugins() as $plugin){ foreach($this->server->getPluginManager()->getPlugins() as $plugin){
$filePath = Filesystem::cleanPath($file->getValue($plugin)); $filePath = Filesystem::cleanPath($file->getValue($plugin));
if(strpos($frameCleanPath, $filePath) === 0){ if(strpos($frameCleanPath, $filePath) === 0){
$this->data["plugin"] = $plugin->getName(); $this->data->plugin = $plugin->getName();
$this->addLine("BAD PLUGIN: " . $plugin->getDescription()->getFullName()); $this->addLine("BAD PLUGIN: " . $plugin->getDescription()->getFullName());
break; break;
} }
@ -341,19 +330,20 @@ class CrashDump{
); );
} }
$this->data["general"] = []; $this->data->general = new CrashDumpDataGeneral(
$this->data["general"]["name"] = $this->server->getName(); name: $this->server->getName(),
$this->data["general"]["base_version"] = VersionInfo::BASE_VERSION; base_version: VersionInfo::BASE_VERSION,
$this->data["general"]["build"] = VersionInfo::BUILD_NUMBER; build: VersionInfo::BUILD_NUMBER,
$this->data["general"]["is_dev"] = VersionInfo::IS_DEVELOPMENT_BUILD; is_dev: VersionInfo::IS_DEVELOPMENT_BUILD,
$this->data["general"]["protocol"] = ProtocolInfo::CURRENT_PROTOCOL; protocol: ProtocolInfo::CURRENT_PROTOCOL,
$this->data["general"]["git"] = VersionInfo::GIT_HASH(); git: VersionInfo::GIT_HASH(),
$this->data["general"]["uname"] = php_uname("a"); uname: php_uname("a"),
$this->data["general"]["php"] = phpversion(); php: PHP_VERSION,
$this->data["general"]["zend"] = zend_version(); zend: zend_version(),
$this->data["general"]["php_os"] = PHP_OS; php_os: PHP_OS,
$this->data["general"]["os"] = Utils::getOS(); os: Utils::getOS(),
$this->data["general"]["composer_libraries"] = $composerLibraries; composer_libraries: $composerLibraries,
);
$this->addLine($this->server->getName() . " version: " . $version->getFullVersion(true) . " [Protocol " . ProtocolInfo::CURRENT_PROTOCOL . "]"); $this->addLine($this->server->getName() . " version: " . $version->getFullVersion(true) . " [Protocol " . ProtocolInfo::CURRENT_PROTOCOL . "]");
$this->addLine("Git commit: " . VersionInfo::GIT_HASH()); $this->addLine("Git commit: " . VersionInfo::GIT_HASH());
$this->addLine("uname -a: " . php_uname("a")); $this->addLine("uname -a: " . php_uname("a"));

View File

@ -1504,8 +1504,8 @@ class Server{
} }
@touch($stamp); //update file timestamp @touch($stamp); //update file timestamp
$plugin = $dump->getData()["plugin"]; $plugin = $dump->getData()->plugin;
if(is_string($plugin)){ if($plugin !== ""){
$p = $this->pluginManager->getPlugin($plugin); $p = $this->pluginManager->getPlugin($plugin);
if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){ if($p instanceof Plugin and !($p->getPluginLoader() instanceof PharPluginLoader)){
$this->logger->debug("Not sending crashdump due to caused by non-phar plugin"); $this->logger->debug("Not sending crashdump due to caused by non-phar plugin");
@ -1513,7 +1513,7 @@ class Server{
} }
} }
if($dump->getData()["error"]["type"] === \ParseError::class){ if($dump->getData()->error["type"] === \ParseError::class){
$report = false; $report = false;
} }

View File

@ -0,0 +1,84 @@
<?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\crash;
final class CrashDumpData implements \JsonSerializable{
public int $format_version;
public float $time;
public float $uptime;
/** @var mixed[] */
public array $lastError = [];
/** @var mixed[] */
public array $error;
public string $plugin_involvement;
public string $plugin = "";
/** @var string[] */
public array $code = [];
/** @var string[] */
public array $trace;
/**
* @var CrashDumpDataPluginEntry[]
* @phpstan-var array<string, CrashDumpDataPluginEntry>
*/
public array $plugins = [];
/** @var string[] */
public array $parameters = [];
public string $serverDotProperties = "";
public string $pocketmineDotYml = "";
/**
* @var string[]
* @phpstan-var array<string, string>
*/
public array $extensions = [];
public string $phpinfo = "";
public CrashDumpDataGeneral $general;
/**
* @return mixed[]
*/
public function jsonSerialize() : array{
$result = (array) $this;
unset($result["serverDotProperties"]);
unset($result["pocketmineDotYml"]);
$result["pocketmine.yml"] = $this->pocketmineDotYml;
$result["server.properties"] = $this->serverDotProperties;
return $result;
}
}

View File

@ -0,0 +1,46 @@
<?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\crash;
final class CrashDumpDataGeneral{
/**
* @param string[] $composer_libraries
* @phpstan-param array<string, string> $composer_libraries
*/
public function __construct(
public string $name,
public string $base_version,
public int $build,
public bool $is_dev,
public int $protocol,
public string $git,
public string $uname,
public string $php,
public string $zend,
public string $php_os,
public string $os,
public array $composer_libraries,
){}
}

View File

@ -0,0 +1,45 @@
<?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\crash;
final class CrashDumpDataPluginEntry{
/**
* @param string[] $authors
* @param string[] $api
* @param string[] $depends
* @param string[] $softDepends
*/
public function __construct(
public string $name,
public string $version,
public array $authors,
public array $api,
public bool $enabled,
public array $depends,
public array $softDepends,
public string $main,
public string $load,
public string $website,
){}
}