Merge branch 'next-minor'

# Conflicts:
#	resources/vanilla
#	src/plugin/PluginBase.php
#	src/plugin/PluginDescription.php
#	src/pocketmine/Player.php
#	src/pocketmine/network/rcon/RCON.php
#	src/pocketmine/network/rcon/RCONInstance.php
#	src/pocketmine/scheduler/AsyncTask.php
#	src/pocketmine/tile/Spawnable.php
#	src/scheduler/AsyncPool.php
#	src/utils/Config.php
#	src/utils/Timezone.php
#	src/utils/UUID.php
#	src/utils/Utils.php
#	src/world/format/io/region/RegionLoader.php
This commit is contained in:
Dylan K. Taylor 2020-04-19 11:13:41 +01:00
commit 163c3855eb
20 changed files with 139 additions and 58 deletions

View File

@ -691,8 +691,8 @@ class Player extends Human implements CommandSender, ChunkLoader, ChunkListener,
/**
* Resets the player's cooldown time for the given item back to the maximum.
*/
public function resetItemCooldown(Item $item) : void{
$ticks = $item->getCooldownTicks();
public function resetItemCooldown(Item $item, ?int $ticks = null) : void{
$ticks = $ticks ?? $item->getCooldownTicks();
if($ticks > 0){
$this->usedItemsCooldown[$item->getId()] = $this->server->getTick() + $ticks;
}

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\plugin;
use pocketmine\utils\AssumptionFailedError;
use function file_exists;
use function fopen;
use function is_dir;
@ -53,8 +54,10 @@ class DiskResourceProvider implements ResourceProvider{
*/
public function getResource(string $filename){
$filename = rtrim(str_replace("\\", "/", $filename), "/");
if(file_exists($this->file . "/" . $filename)){
return fopen($this->file . "/" . $filename, "rb");
if(file_exists($this->file . "resources/" . $filename)){
$resource = fopen($this->file . "resources/" . $filename, "rb");
if($resource === false) throw new AssumptionFailedError("fopen() should not fail on a file which exists");
return $resource;
}
return null;

View File

@ -30,6 +30,7 @@ use pocketmine\command\PluginCommand;
use pocketmine\command\PluginIdentifiableCommand;
use pocketmine\scheduler\TaskScheduler;
use pocketmine\Server;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Config;
use function count;
use function dirname;
@ -275,7 +276,10 @@ abstract class PluginBase implements Plugin, CommandExecutor{
return false;
}
$ret = stream_copy_to_stream($resource, $fp = fopen($out, "wb")) > 0;
$fp = fopen($out, "wb");
if($fp === false) throw new AssumptionFailedError("fopen() should not fail with wb flags");
$ret = stream_copy_to_stream($resource, $fp) > 0;
fclose($fp);
fclose($resource);
return $ret;

View File

@ -27,7 +27,6 @@ use pocketmine\permission\Permission;
use pocketmine\permission\PermissionParser;
use function array_map;
use function array_values;
use function extension_loaded;
use function is_array;
use function phpversion;
use function preg_match;
@ -229,11 +228,11 @@ class PluginDescription{
*/
public function checkRequiredExtensions() : void{
foreach($this->extensions as $name => $versionConstrs){
if(!extension_loaded($name)){
$gotVersion = phpversion($name);
if($gotVersion === false){
throw new PluginException("Required extension $name not loaded");
}
$gotVersion = phpversion($name);
foreach($versionConstrs as $constr){ // versionConstrs_loop
if($constr === "*"){
continue;

View File

@ -34,6 +34,7 @@ use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\permission\PermissionManager;
use pocketmine\Server;
use pocketmine\timings\TimingsHandler;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Utils;
use function array_intersect;
use function array_map;
@ -208,6 +209,7 @@ class PluginManager{
shuffle($files); //this prevents plugins implicitly relying on the filesystem name order when they should be using dependency properties
foreach($loaders as $loader){
foreach($files as $file){
if(!is_string($file)) throw new AssumptionFailedError("FilesystemIterator current should be string when using CURRENT_AS_PATHNAME");
if(!$loader->canLoadPlugin($file)){
continue;
}

View File

@ -55,7 +55,10 @@ class ScriptPluginLoader implements PluginLoader{
* Gets the PluginDescription from the file
*/
public function getPluginDescription(string $file) : ?PluginDescription{
$content = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$content = @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if($content === false){
return null;
}
$data = [];

View File

@ -52,9 +52,15 @@ class AsyncPool{
/** @var \SplQueue[]|AsyncTask[][] */
private $taskQueues = [];
/** @var AsyncWorker[] */
/**
* @var AsyncWorker[]
* @phpstan-var array<int, AsyncWorker>
*/
private $workers = [];
/** @var int[] */
/**
* @var int[]
* @phpstan-var array<int, int>
*/
private $workerLastUsed = [];
/**

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\scheduler;
use pocketmine\utils\AssumptionFailedError;
use function is_scalar;
use function serialize;
use function spl_object_id;
@ -105,7 +106,11 @@ abstract class AsyncTask extends \Threaded{
* @return mixed
*/
public function getResult(){
return $this->serialized ? unserialize($this->result) : $this->result;
if($this->serialized){
if(!is_string($this->result)) throw new AssumptionFailedError("Result expected to be a serialized string");
return unserialize($this->result);
}
return $this->result;
}
public function cancelRun() : void{

View File

@ -26,6 +26,7 @@ namespace pocketmine\scheduler;
use pocketmine\network\mcpe\protocol\ProtocolInfo;
use pocketmine\player\Player;
use pocketmine\Server;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Internet;
use pocketmine\utils\Process;
use pocketmine\utils\Utils;
@ -149,7 +150,9 @@ class SendUsageTask extends AsyncTask{
}
$this->endpoint = $endpoint . "api/post";
$this->data = json_encode($data/*, JSON_PRETTY_PRINT*/);
$data = json_encode($data/*, JSON_PRETTY_PRINT*/);
if($data === false) throw new AssumptionFailedError("Statistics JSON should never fail to encode: " . json_last_error_msg());
$this->data = $data;
}
public function onRun() : void{

View File

@ -166,6 +166,9 @@ class Config{
$this->save();
}else{
$content = file_get_contents($this->file);
if($content === false){
throw new \RuntimeException("Unable to load config file");
}
$config = null;
switch($this->type){
case Config::PROPERTIES:
@ -539,7 +542,6 @@ class Config{
/**
* @return mixed[]
* @phpstan-return array<string, mixed>
*/
private function parseProperties(string $content) : array{
$result = [];

View File

@ -58,6 +58,7 @@ final class Filesystem{
public static function recursiveUnlink(string $dir) : void{
if(is_dir($dir)){
$objects = scandir($dir, SCANDIR_SORT_NONE);
if($objects === false) throw new AssumptionFailedError("scandir() shouldn't return false when is_dir() returns true");
foreach($objects as $object){
if($object !== "." and $object !== ".."){
if(is_dir($dir . "/" . $object)){

View File

@ -72,7 +72,7 @@ class Internet{
*
* @param bool $force default false, force IP check even when cached
*
* @return string|bool
* @return string|false
*/
public static function getIP(bool $force = false){
if(!self::$online){
@ -116,7 +116,10 @@ class Internet{
* @throws InternetException
*/
public static function getInternalIP() : string{
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
$sock = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
if($sock === false){
throw new InternetException("Failed to get internal IP: " . trim(socket_strerror(socket_last_error())));
}
try{
if(!@socket_connect($sock, "8.8.8.8", 65534)){
throw new InternetException("Failed to get internal IP: " . trim(socket_strerror(socket_last_error($sock))));
@ -205,6 +208,9 @@ class Internet{
}
$ch = curl_init($page);
if($ch === false){
throw new InternetException("Unable to create new cURL session");
}
curl_setopt_array($ch, $extraOpts + [
CURLOPT_SSL_VERIFYPEER => false,
@ -221,10 +227,10 @@ class Internet{
]);
try{
$raw = curl_exec($ch);
$error = curl_error($ch);
if($error !== ""){
throw new InternetException($error);
if($raw === false){
throw new InternetException(curl_error($ch));
}
if(!is_string($raw)) throw new AssumptionFailedError("curl_exec() should return string|false when CURLOPT_RETURNTRANSFER is set");
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$rawHeaders = substr($raw, 0, $headerSize);

View File

@ -55,13 +55,16 @@ final class Process{
$VmSize = null;
$VmRSS = null;
if(Utils::getOS() === "linux" or Utils::getOS() === "android"){
$status = file_get_contents("/proc/self/status");
$status = @file_get_contents("/proc/self/status");
if($status === false) throw new AssumptionFailedError("/proc/self/status should always be accessible");
// the numbers found here should never be bigger than PHP_INT_MAX, so we expect them to always be castable to int
if(preg_match("/VmRSS:[ \t]+([0-9]+) kB/", $status, $matches) > 0){
$VmRSS = $matches[1] * 1024;
$VmRSS = ((int) $matches[1]) * 1024;
}
if(preg_match("/VmSize:[ \t]+([0-9]+) kB/", $status, $matches) > 0){
$VmSize = $matches[1] * 1024;
$VmSize = ((int) $matches[1]) * 1024;
}
}
@ -90,13 +93,14 @@ final class Process{
$heap = 0;
if(Utils::getOS() === "linux" or Utils::getOS() === "android"){
$mappings = file("/proc/self/maps");
$mappings = @file("/proc/self/maps");
if($mappings === false) throw new AssumptionFailedError("/proc/self/maps should always be accessible");
foreach($mappings as $line){
if(preg_match("#([a-z0-9]+)\\-([a-z0-9]+) [rwxp\\-]{4} [a-z0-9]+ [^\\[]*\\[([a-zA-z0-9]+)\\]#", trim($line), $matches) > 0){
if(strpos($matches[3], "heap") === 0){
$heap += hexdec($matches[2]) - hexdec($matches[1]);
$heap += (int) hexdec($matches[2]) - (int) hexdec($matches[1]);
}elseif(strpos($matches[3], "stack") === 0){
$stack += hexdec($matches[2]) - hexdec($matches[1]);
$stack += (int) hexdec($matches[2]) - (int) hexdec($matches[1]);
}
}
}
@ -107,7 +111,9 @@ final class Process{
public static function getThreadCount() : int{
if(Utils::getOS() === "linux" or Utils::getOS() === "android"){
if(preg_match("/Threads:[ \t]+([0-9]+)/", file_get_contents("/proc/self/status"), $matches) > 0){
$status = @file_get_contents("/proc/self/status");
if($status === false) throw new AssumptionFailedError("/proc/self/status should always be accessible");
if(preg_match("/Threads:[ \t]+([0-9]+)/", $status, $matches) > 0){
return (int) $matches[1];
}
}

View File

@ -91,6 +91,7 @@ abstract class Terminal{
private static function detectFormattingCodesSupport() : bool{
$stdout = fopen("php://stdout", "w");
if($stdout === false) throw new AssumptionFailedError("Opening php://stdout should never fail");
$result = (
stream_isatty($stdout) and //STDOUT isn't being piped
(

View File

@ -66,13 +66,19 @@ abstract class TextFormat{
public const ITALIC = TextFormat::ESCAPE . "o";
public const RESET = TextFormat::ESCAPE . "r";
private static function makePcreError(string $info) : \InvalidArgumentException{
throw new \InvalidArgumentException("$info: Encountered PCRE error " . preg_last_error() . " during regex operation");
}
/**
* Splits the string by Format tokens
*
* @return string[]
*/
public static function tokenize(string $string) : array{
return preg_split("/(" . TextFormat::ESCAPE . "[0-9a-fk-or])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
$result = preg_split("/(" . TextFormat::ESCAPE . "[0-9a-fk-or])/u", $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
if($result === false) throw self::makePcreError("Failed to tokenize string");
return $result;
}
/**
@ -83,6 +89,7 @@ abstract class TextFormat{
public static function clean(string $string, bool $removeFormat = true) : string{
$string = mb_scrub($string, 'UTF-8');
$string = preg_replace("/[\x{E000}-\x{F8FF}]/u", "", $string); //remove unicode private-use-area characters (they might break the console)
if($string === null) throw self::makePcreError("Failed to strip private-area characters");
if($removeFormat){
$string = str_replace(TextFormat::ESCAPE, "", preg_replace("/" . TextFormat::ESCAPE . "[0-9a-fk-or]/u", "", $string));
}
@ -281,7 +288,11 @@ abstract class TextFormat{
}
}
return json_encode($newString, JSON_UNESCAPED_SLASHES);
$result = json_encode($newString, JSON_UNESCAPED_SLASHES);
if($result === false){
throw new \InvalidArgumentException("Failed to encode result JSON: " . json_last_error_msg());
}
return $result;
}
/**

View File

@ -48,7 +48,11 @@ use function trim;
abstract class Timezone{
public static function get() : string{
return ini_get('date.timezone');
$tz = ini_get('date.timezone');
if($tz === false){
throw new AssumptionFailedError('date.timezone INI entry should always exist');
}
return $tz;
}
public static function init() : void{
@ -138,19 +142,16 @@ abstract class Timezone{
return self::parseOffset($offset);
case 'linux':
// Ubuntu / Debian.
if(file_exists('/etc/timezone')){
$data = file_get_contents('/etc/timezone');
if($data){
return trim($data);
}
$data = @file_get_contents('/etc/timezone');
if($data !== false){
return trim($data);
}
// RHEL / CentOS
if(file_exists('/etc/sysconfig/clock')){
$data = parse_ini_file('/etc/sysconfig/clock');
if(isset($data['ZONE']) and is_string($data['ZONE'])){
return trim($data['ZONE']);
}
$data = @parse_ini_file('/etc/sysconfig/clock');
if($data !== false and isset($data['ZONE']) and is_string($data['ZONE'])){
return trim($data['ZONE']);
}
//Portable method for incompatible linux distributions.
@ -163,12 +164,10 @@ abstract class Timezone{
return self::parseOffset($offset);
case 'mac':
if(is_link('/etc/localtime')){
$filename = readlink('/etc/localtime');
if(strpos($filename, '/usr/share/zoneinfo/') === 0){
$timezone = substr($filename, 20);
return trim($timezone);
}
$filename = @readlink('/etc/localtime');
if($filename !== false and strpos($filename, '/usr/share/zoneinfo/') === 0){
$timezone = substr($filename, 20);
return trim($timezone);
}
return false;
@ -180,7 +179,7 @@ abstract class Timezone{
/**
* @param string $offset In the format of +09:00, +02:00, -04:00 etc.
*
* @return string|bool
* @return string|false
*/
private static function parseOffset($offset){
//Make signed offsets unsigned for date_parse

View File

@ -61,7 +61,12 @@ final class UUID{
* Creates an UUID from an hexadecimal representation
*/
public static function fromString(string $uuid, ?int $version = null) : UUID{
return self::fromBinary(hex2bin(str_replace("-", "", trim($uuid))), $version);
//TODO: should we be stricter about the notation (8-4-4-4-12)?
$binary = @hex2bin(str_replace("-", "", trim($uuid)));
if($binary === false){
throw new \InvalidArgumentException("Invalid hex string UUID representation");
}
return self::fromBinary($binary, $version);
}
/**

View File

@ -114,7 +114,12 @@ class Utils{
//non-class function
return $func->getName();
}
return "closure@" . Filesystem::cleanPath($func->getFileName()) . "#L" . $func->getStartLine();
$filename = $func->getFileName();
return "closure@" . ($filename !== false ?
Filesystem::cleanPath($filename) . "#L" . $func->getStartLine() :
"internal"
);
}
/**
@ -125,7 +130,12 @@ class Utils{
public static function getNiceClassName(object $obj) : string{
$reflect = new \ReflectionClass($obj);
if($reflect->isAnonymous()){
return "anonymous@" . Filesystem::cleanPath($reflect->getFileName()) . "#L" . $reflect->getStartLine();
$filename = $reflect->getFileName();
return "anonymous@" . ($filename !== false ?
Filesystem::cleanPath($filename) . "#L" . $reflect->getStartLine() :
"internal"
);
}
return $reflect->getName();
@ -169,14 +179,14 @@ class Utils{
}
$machine = php_uname("a");
$machine .= file_exists("/proc/cpuinfo") ? implode(preg_grep("/(model name|Processor|Serial)/", file("/proc/cpuinfo"))) : "";
$machine .= ($cpuinfo = @file("/proc/cpuinfo")) !== false ? implode(preg_grep("/(model name|Processor|Serial)/", $cpuinfo)) : "";
$machine .= sys_get_temp_dir();
$machine .= $extra;
$os = Utils::getOS();
if($os === "win"){
@exec("ipconfig /ALL", $mac);
$mac = implode("\n", $mac);
if(preg_match_all("#Physical Address[. ]{1,}: ([0-9A-F\\-]{17})#", $mac, $matches)){
if(preg_match_all("#Physical Address[. ]{1,}: ([0-9A-F\\-]{17})#", $mac, $matches) > 0){
foreach($matches[1] as $i => $v){
if($v == "00-00-00-00-00-00"){
unset($matches[1][$i]);
@ -190,7 +200,7 @@ class Utils{
}else{
@exec("ifconfig 2>/dev/null", $mac);
$mac = implode("\n", $mac);
if(preg_match_all("#HWaddr[ \t]{1,}([0-9a-f:]{17})#", $mac, $matches)){
if(preg_match_all("#HWaddr[ \t]{1,}([0-9a-f:]{17})#", $mac, $matches) > 0){
foreach($matches[1] as $i => $v){
if($v == "00:00:00:00:00:00"){
unset($matches[1][$i]);
@ -270,14 +280,14 @@ class Utils{
switch(Utils::getOS()){
case "linux":
case "android":
if(file_exists("/proc/cpuinfo")){
foreach(file("/proc/cpuinfo") as $l){
if(($cpuinfo = @file('/proc/cpuinfo')) !== false){
foreach($cpuinfo as $l){
if(preg_match('/^processor[ \t]*:[ \t]*[0-9]+$/m', $l) > 0){
++$processors;
}
}
}elseif(is_readable("/sys/devices/system/cpu/present")){
if(preg_match("/^([0-9]+)\\-([0-9]+)$/", trim(file_get_contents("/sys/devices/system/cpu/present")), $matches) > 0){
}elseif(($cpuPresent = @file_get_contents("/sys/devices/system/cpu/present")) !== false){
if(preg_match("/^([0-9]+)\\-([0-9]+)$/", trim($cpuPresent), $matches) > 0){
$processors = (int) ($matches[2] - $matches[1]);
}
}
@ -382,7 +392,9 @@ class Utils{
public static function getReferenceCount($value, bool $includeCurrent = true) : int{
ob_start();
debug_zval_dump($value);
$ret = explode("\n", ob_get_contents());
$contents = ob_get_contents();
if($contents === false) throw new AssumptionFailedError("ob_get_contents() should never return false here");
$ret = explode("\n", $contents);
ob_end_clean();
if(count($ret) >= 1 and preg_match('/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){
@ -462,6 +474,10 @@ class Utils{
return array_combine($matches[1], $matches[2]);
}
/**
* @phpstan-param class-string $className
* @phpstan-param class-string $baseName
*/
public static function testValidInstance(string $className, string $baseName) : void{
try{
$base = new \ReflectionClass($baseName);

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace pocketmine\world\format\io\region;
use pocketmine\utils\AssumptionFailedError;
use pocketmine\utils\Binary;
use pocketmine\world\format\ChunkException;
use pocketmine\world\format\io\exception\CorruptedChunkException;
@ -88,7 +89,9 @@ class RegionLoader{
throw new CorruptedRegionException("Region file should be padded to a multiple of 4KiB");
}
$this->filePointer = fopen($this->filePath, "r+b");
$filePointer = fopen($this->filePath, "r+b");
if($filePointer === false) throw new AssumptionFailedError("fopen() should not fail here");
$this->filePointer = $filePointer;
stream_set_read_buffer($this->filePointer, 1024 * 16); //16KB
stream_set_write_buffer($this->filePointer, 1024 * 16); //16KB
if(!$exists){

View File

@ -82,6 +82,12 @@ parameters:
count: 1
path: ../../../src/network/mcpe/protocol/types/entity/EntityMetadataCollection.php
-
#readlink() can return false but phpstan doesn't know this
message: "#^Strict comparison using \\!\\=\\= between string and false will always evaluate to true\\.$#"
count: 1
path: ../../../src/pocketmine/utils/Timezone.php
-
#phpstan doesn't understand that SplFixedArray may contain null
message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertNotNull\\(\\) with int and string will always evaluate to true\\.$#"