PocketMine-MP 5.23.3 git-976fc63567edab7a6fb6aeae739f43cf9fe57de4
Loading...
Searching...
No Matches
Utils.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
28namespace pocketmine\utils;
29
35use Ramsey\Uuid\Uuid;
36use Ramsey\Uuid\UuidInterface;
37use function array_combine;
38use function array_map;
39use function array_reverse;
40use function array_values;
41use function bin2hex;
42use function chunk_split;
43use function class_exists;
44use function count;
45use function debug_zval_dump;
46use function dechex;
47use function exec;
48use function explode;
49use function file;
50use function file_exists;
51use function file_get_contents;
52use function function_exists;
53use function get_class;
54use function get_current_user;
55use function get_loaded_extensions;
56use function getenv;
57use function gettype;
58use function implode;
59use function interface_exists;
60use function is_a;
61use function is_array;
62use function is_bool;
63use function is_float;
64use function is_infinite;
65use function is_int;
66use function is_nan;
67use function is_object;
68use function is_string;
69use function mb_check_encoding;
70use function mt_getrandmax;
71use function mt_rand;
72use function ob_end_clean;
73use function ob_get_contents;
74use function ob_start;
75use function opcache_get_status;
76use function ord;
77use function php_uname;
78use function phpversion;
79use function preg_grep;
80use function preg_match;
81use function preg_match_all;
82use function preg_replace;
83use function shell_exec;
84use function spl_object_id;
85use function str_contains;
86use function str_pad;
87use function str_split;
88use function str_starts_with;
89use function stripos;
90use function strlen;
91use function substr;
92use function sys_get_temp_dir;
93use function trim;
94use function xdebug_get_function_stack;
95use const PHP_EOL;
96use const PHP_INT_MAX;
97use const PHP_INT_SIZE;
98use const PHP_MAXPATHLEN;
99use const STR_PAD_LEFT;
100use const STR_PAD_RIGHT;
101
105final class Utils{
106 public const OS_WINDOWS = "win";
107 public const OS_IOS = "ios";
108 public const OS_MACOS = "mac";
109 public const OS_ANDROID = "android";
110 public const OS_LINUX = "linux";
111 public const OS_BSD = "bsd";
112 public const OS_UNKNOWN = "other";
113
114 private static ?string $os = null;
115 private static ?UuidInterface $serverUniqueId = null;
116 private static ?int $cpuCores = null;
117
124 public static function getNiceClosureName(\Closure $closure) : string{
125 $func = new \ReflectionFunction($closure);
126 if(!str_contains($func->getName(), '{closure')){
127 //closure wraps a named function, can be done with reflection or fromCallable()
128 //isClosure() is useless here because it just tells us if $func is reflecting a Closure object
129
130 $scope = $func->getClosureScopeClass();
131 if($scope !== null){ //class method
132 return
133 $scope->getName() .
134 ($func->getClosureThis() !== null ? "->" : "::") .
135 $func->getName(); //name doesn't include class in this case
136 }
137
138 //non-class function
139 return $func->getName();
140 }
141 $filename = $func->getFileName();
142
143 return "closure@" . ($filename !== false ?
144 Filesystem::cleanPath($filename) . "#L" . $func->getStartLine() :
145 "internal"
146 );
147 }
148
154 public static function getNiceClassName(object $obj) : string{
155 $reflect = new \ReflectionClass($obj);
156 if($reflect->isAnonymous()){
157 $filename = $reflect->getFileName();
158
159 return "anonymous@" . ($filename !== false ?
160 Filesystem::cleanPath($filename) . "#L" . $reflect->getStartLine() :
161 "internal"
162 );
163 }
164
165 return $reflect->getName();
166 }
167
172 public static function cloneCallback() : \Closure{
173 return static function(object $o){
174 return clone $o;
175 };
176 }
177
188 public static function cloneObjectArray(array $array) : array{
189 return array_map(fn(object $o) => clone $o, $array);
190 }
191
200 public static function getMachineUniqueId(string $extra = "") : UuidInterface{
201 if(self::$serverUniqueId !== null && $extra === ""){
202 return self::$serverUniqueId;
203 }
204
205 $machine = php_uname("a");
206 $cpuinfo = @file("/proc/cpuinfo");
207 if($cpuinfo !== false){
208 $cpuinfoLines = preg_grep("/(model name|Processor|Serial)/", $cpuinfo);
209 if($cpuinfoLines === false){
210 throw new AssumptionFailedError("Pattern is valid, so this shouldn't fail ...");
211 }
212 $machine .= implode("", $cpuinfoLines);
213 }
214 $machine .= sys_get_temp_dir();
215 $machine .= $extra;
216 $os = Utils::getOS();
217 if($os === Utils::OS_WINDOWS){
218 @exec("ipconfig /ALL", $mac);
219 $mac = implode("\n", $mac);
220 if(preg_match_all("#Physical Address[. ]{1,}: ([0-9A-F\\-]{17})#", $mac, $matches) > 0){
221 foreach($matches[1] as $i => $v){
222 if($v === "00-00-00-00-00-00"){
223 unset($matches[1][$i]);
224 }
225 }
226 $machine .= implode(" ", $matches[1]); //Mac Addresses
227 }
228 }elseif($os === Utils::OS_LINUX){
229 if(file_exists("/etc/machine-id")){
230 $machine .= file_get_contents("/etc/machine-id");
231 }else{
232 @exec("ifconfig 2>/dev/null", $mac);
233 $mac = implode("\n", $mac);
234 if(preg_match_all("#HWaddr[ \t]{1,}([0-9a-f:]{17})#", $mac, $matches) > 0){
235 foreach($matches[1] as $i => $v){
236 if($v === "00:00:00:00:00:00"){
237 unset($matches[1][$i]);
238 }
239 }
240 $machine .= implode(" ", $matches[1]); //Mac Addresses
241 }
242 }
243 }elseif($os === Utils::OS_ANDROID){
244 $machine .= @file_get_contents("/system/build.prop");
245 }elseif($os === Utils::OS_MACOS){
246 $machine .= shell_exec("system_profiler SPHardwareDataType | grep UUID");
247 }
248 $data = $machine . PHP_MAXPATHLEN;
249 $data .= PHP_INT_MAX;
250 $data .= PHP_INT_SIZE;
251 $data .= get_current_user();
252 foreach(get_loaded_extensions() as $ext){
253 $data .= $ext . ":" . phpversion($ext);
254 }
255
256 //TODO: use of NIL as namespace is a hack; it works for now, but we should have a proper namespace UUID
257 $uuid = Uuid::uuid3(Uuid::NIL, $data);
258
259 if($extra === ""){
260 self::$serverUniqueId = $uuid;
261 }
262
263 return $uuid;
264 }
265
276 public static function getOS(bool $recalculate = false) : string{
277 if(self::$os === null || $recalculate){
278 $uname = php_uname("s");
279 if(stripos($uname, "Darwin") !== false){
280 if(str_starts_with(php_uname("m"), "iP")){
281 self::$os = self::OS_IOS;
282 }else{
283 self::$os = self::OS_MACOS;
284 }
285 }elseif(stripos($uname, "Win") !== false || $uname === "Msys"){
286 self::$os = self::OS_WINDOWS;
287 }elseif(stripos($uname, "Linux") !== false){
288 if(@file_exists("/system/build.prop")){
289 self::$os = self::OS_ANDROID;
290 }else{
291 self::$os = self::OS_LINUX;
292 }
293 }elseif(stripos($uname, "BSD") !== false || $uname === "DragonFly"){
294 self::$os = self::OS_BSD;
295 }else{
296 self::$os = self::OS_UNKNOWN;
297 }
298 }
299
300 return self::$os;
301 }
302
303 public static function getCoreCount(bool $recalculate = false) : int{
304 if(self::$cpuCores !== null && !$recalculate){
305 return self::$cpuCores;
306 }
307
308 $processors = 0;
309 switch(Utils::getOS()){
310 case Utils::OS_LINUX:
311 case Utils::OS_ANDROID:
312 if(($cpuinfo = @file('/proc/cpuinfo')) !== false){
313 foreach($cpuinfo as $l){
314 if(preg_match('/^processor[ \t]*:[ \t]*[0-9]+$/m', $l) > 0){
315 ++$processors;
316 }
317 }
318 }elseif(($cpuPresent = @file_get_contents("/sys/devices/system/cpu/present")) !== false){
319 if(preg_match("/^([0-9]+)\\-([0-9]+)$/", trim($cpuPresent), $matches) > 0){
320 $processors = ((int) $matches[2]) - ((int) $matches[1]);
321 }
322 }
323 break;
324 case Utils::OS_BSD:
325 case Utils::OS_MACOS:
326 $processors = (int) shell_exec("sysctl -n hw.ncpu");
327 break;
328 case Utils::OS_WINDOWS:
329 $processors = (int) getenv("NUMBER_OF_PROCESSORS");
330 break;
331 }
332 return self::$cpuCores = $processors;
333 }
334
338 public static function hexdump(string $bin) : string{
339 $output = "";
340 $bin = str_split($bin, 16);
341 foreach($bin as $counter => $line){
342 $hex = chunk_split(chunk_split(str_pad(bin2hex($line), 32, " ", STR_PAD_RIGHT), 2, " "), 24, " ");
343 $ascii = preg_replace('#([^\x20-\x7E])#', ".", $line);
344 $output .= str_pad(dechex($counter << 4), 4, "0", STR_PAD_LEFT) . " " . $hex . " " . $ascii . PHP_EOL;
345 }
346
347 return $output;
348 }
349
353 public static function printable(mixed $str) : string{
354 if(!is_string($str)){
355 return gettype($str);
356 }
357
358 return preg_replace('#([^\x20-\x7E])#', '.', $str);
359 }
360
361 public static function javaStringHash(string $string) : int{
362 $hash = 0;
363 for($i = 0, $len = strlen($string); $i < $len; $i++){
364 $ord = ord($string[$i]);
365 if(($ord & 0x80) !== 0){
366 $ord -= 0x100;
367 }
368 $hash = 31 * $hash + $ord;
369 $hash &= 0xFFFFFFFF;
370 }
371 return $hash;
372 }
373
374 public static function getReferenceCount(object $value, bool $includeCurrent = true) : int{
375 ob_start();
376 debug_zval_dump($value);
377 $contents = ob_get_contents();
378 if($contents === false) throw new AssumptionFailedError("ob_get_contents() should never return false here");
379 $ret = explode("\n", $contents);
380 ob_end_clean();
381
382 if(preg_match('/^.* refcount\\(([0-9]+)\\)\\{$/', trim($ret[0]), $m) > 0){
383 return ((int) $m[1]) - ($includeCurrent ? 3 : 4); //$value + zval call + extra call
384 }
385 return -1;
386 }
387
388 private static function printableExceptionMessage(\Throwable $e) : string{
389 $errstr = preg_replace('/\s+/', ' ', trim($e->getMessage()));
390
391 $errno = $e->getCode();
392 if(is_int($errno)){
393 try{
394 $errno = ErrorTypeToStringMap::get($errno);
395 }catch(\InvalidArgumentException $ex){
396 //pass
397 }
398 }
399
400 $errfile = Filesystem::cleanPath($e->getFile());
401 $errline = $e->getLine();
402
403 return get_class($e) . ": \"$errstr\" ($errno) in \"$errfile\" at line $errline";
404 }
405
411 public static function printableExceptionInfo(\Throwable $e, $trace = null) : array{
412 if($trace === null){
413 $trace = $e->getTrace();
414 }
415
416 $lines = [self::printableExceptionMessage($e)];
417 $lines[] = "--- Stack trace ---";
418 foreach(Utils::printableTrace($trace) as $line){
419 $lines[] = " " . $line;
420 }
421 for($prev = $e->getPrevious(); $prev !== null; $prev = $prev->getPrevious()){
422 $lines[] = "--- Previous ---";
423 $lines[] = self::printableExceptionMessage($prev);
424 foreach(Utils::printableTrace($prev->getTrace()) as $line){
425 $lines[] = " " . $line;
426 }
427 }
428 $lines[] = "--- End of exception information ---";
429 return $lines;
430 }
431
432 private static function stringifyValueForTrace(mixed $value, int $maxStringLength) : string{
433 return match(true){
434 is_object($value) => "object " . self::getNiceClassName($value) . "#" . spl_object_id($value),
435 is_array($value) => "array[" . count($value) . "]",
436 is_string($value) => "string[" . strlen($value) . "] " . substr(Utils::printable($value), 0, $maxStringLength),
437 is_bool($value) => $value ? "true" : "false",
438 is_int($value) => "int " . $value,
439 is_float($value) => "float " . $value,
440 $value === null => "null",
441 default => gettype($value) . " " . Utils::printable((string) $value)
442 };
443 }
444
452 public static function printableTrace(array $trace, int $maxStringLength = 80) : array{
453 $messages = [];
454 for($i = 0; isset($trace[$i]); ++$i){
455 $params = "";
456 if(isset($trace[$i]["args"]) || isset($trace[$i]["params"])){
457 if(isset($trace[$i]["args"])){
458 $args = $trace[$i]["args"];
459 }else{
460 $args = $trace[$i]["params"];
461 }
464 $paramsList = [];
465 $offset = 0;
466 foreach($args as $argId => $value){
467 $paramsList[] = ($argId === $offset ? "" : "$argId: ") . self::stringifyValueForTrace($value, $maxStringLength);
468 $offset++;
469 }
470 $params = implode(", ", $paramsList);
471 }
472 $messages[] = "#$i " .
473 (isset($trace[$i]["file"]) ? Filesystem::cleanPath($trace[$i]["file"]) : "") .
474 "(" . (isset($trace[$i]["line"]) ? $trace[$i]["line"] : "") . "): " .
475 (isset($trace[$i]["class"]) ?
476 $trace[$i]["class"] . (($trace[$i]["type"] === "dynamic" || $trace[$i]["type"] === "->") ? "->" : "::") :
477 ""
478 ) .
479 $trace[$i]["function"] .
480 "(" . Utils::printable($params) . ")";
481 }
482 return $messages;
483 }
484
494 public static function printableTraceWithMetadata(array $rawTrace, int $maxStringLength = 80) : array{
495 $printableTrace = self::printableTrace($rawTrace, $maxStringLength);
496 $safeTrace = [];
497 foreach($printableTrace as $frameId => $printableFrame){
498 $rawFrame = $rawTrace[$frameId];
499 $safeTrace[$frameId] = new ThreadCrashInfoFrame(
500 $printableFrame,
501 $rawFrame["file"] ?? null,
502 $rawFrame["line"] ?? 0
503 );
504 }
505
506 return $safeTrace;
507 }
508
513 public static function currentTrace(int $skipFrames = 0) : array{
514 ++$skipFrames; //omit this frame from trace, in addition to other skipped frames
515 if(function_exists("xdebug_get_function_stack") && count($trace = @xdebug_get_function_stack()) !== 0){
516 $trace = array_reverse($trace);
517 }else{
518 $e = new \Exception();
519 $trace = $e->getTrace();
520 }
521 for($i = 0; $i < $skipFrames; ++$i){
522 unset($trace[$i]);
523 }
524 return array_values($trace);
525 }
526
530 public static function printableCurrentTrace(int $skipFrames = 0) : array{
531 return self::printableTrace(self::currentTrace(++$skipFrames));
532 }
533
539 public static function parseDocComment(string $docComment) : array{
540 $rawDocComment = substr($docComment, 3, -2); //remove the opening and closing markers
541 preg_match_all('/(*ANYCRLF)^[\t ]*(?:\* )?@([a-zA-Z\-]+)(?:[\t ]+(.+?))?[\t ]*$/m', $rawDocComment, $matches);
542
543 return array_combine($matches[1], $matches[2]);
544 }
545
550 public static function testValidInstance(string $className, string $baseName) : void{
551 $baseInterface = false;
552 if(!class_exists($baseName)){
553 if(!interface_exists($baseName)){
554 throw new \InvalidArgumentException("Base class $baseName does not exist");
555 }
556 $baseInterface = true;
557 }
558 if(!class_exists($className)){
559 throw new \InvalidArgumentException("Class $className does not exist or is not a class");
560 }
561 if(!is_a($className, $baseName, true)){
562 throw new \InvalidArgumentException("Class $className does not " . ($baseInterface ? "implement" : "extend") . " $baseName");
563 }
564 $class = new \ReflectionClass($className);
565 if(!$class->isInstantiable()){
566 throw new \InvalidArgumentException("Class $className cannot be constructed");
567 }
568 }
569
582 public static function validateCallableSignature(callable|CallbackType $signature, callable $subject) : void{
583 if(!($signature instanceof CallbackType)){
584 $signature = CallbackType::createFromCallable($signature);
585 }
586 if(!$signature->isSatisfiedBy($subject)){
587 throw new \TypeError("Declaration of callable `" . CallbackType::createFromCallable($subject) . "` must be compatible with `" . $signature . "`");
588 }
589 }
590
596 public static function validateArrayValueType(array $array, \Closure $validator) : void{
597 foreach($array as $k => $v){
598 try{
599 $validator($v);
600 }catch(\TypeError $e){
601 throw new \TypeError("Incorrect type of element at \"$k\": " . $e->getMessage(), 0, $e);
602 }
603 }
604 }
605
616 public static function stringifyKeys(array $array) : \Generator{
617 foreach($array as $key => $value){ // @phpstan-ignore-line - this is where we fix the stupid bullshit with array keys :)
618 yield (string) $key => $value;
619 }
620 }
621
630 public static function promoteKeys(array $array) : array{
631 return $array;
632 }
633
634 public static function checkUTF8(string $string) : void{
635 if(!mb_check_encoding($string, 'UTF-8')){
636 throw new \InvalidArgumentException("Text must be valid UTF-8");
637 }
638 }
639
646 public static function assumeNotFalse(mixed $value, \Closure|string $context = "This should never be false") : mixed{
647 if($value === false){
648 throw new AssumptionFailedError("Assumption failure: " . (is_string($context) ? $context : $context()) . " (THIS IS A BUG)");
649 }
650 return $value;
651 }
652
653 public static function checkFloatNotInfOrNaN(string $name, float $float) : void{
654 if(is_nan($float)){
655 throw new \InvalidArgumentException("$name cannot be NaN");
656 }
657 if(is_infinite($float)){
658 throw new \InvalidArgumentException("$name cannot be infinite");
659 }
660 }
661
662 public static function checkVector3NotInfOrNaN(Vector3 $vector3) : void{
663 if($vector3 instanceof Location){ //location could be masquerading as vector3
664 self::checkFloatNotInfOrNaN("yaw", $vector3->yaw);
665 self::checkFloatNotInfOrNaN("pitch", $vector3->pitch);
666 }
667 self::checkFloatNotInfOrNaN("x", $vector3->x);
668 self::checkFloatNotInfOrNaN("y", $vector3->y);
669 self::checkFloatNotInfOrNaN("z", $vector3->z);
670 }
671
672 public static function checkLocationNotInfOrNaN(Location $location) : void{
673 self::checkVector3NotInfOrNaN($location);
674 }
675
680 public static function getOpcacheJitMode() : ?int{
681 if(
682 function_exists('opcache_get_status') &&
683 ($opcacheStatus = opcache_get_status(false)) !== false &&
684 isset($opcacheStatus["jit"]["on"])
685 ){
686 $jit = $opcacheStatus["jit"];
687 if($jit["on"] === true){
688 return (($jit["opt_flags"] >> 2) * 1000) +
689 (($jit["opt_flags"] & 0x03) * 100) +
690 ($jit["kind"] * 10) +
691 $jit["opt_level"];
692 }
693
694 //jit available, but disabled
695 return 0;
696 }
697
698 //jit not available
699 return null;
700 }
701
706 public static function getRandomFloat() : float{
707 return mt_rand() / mt_getrandmax();
708 }
709}
static printableExceptionInfo(\Throwable $e, $trace=null)
Definition Utils.php:411
static parseDocComment(string $docComment)
Definition Utils.php:539
static assumeNotFalse(mixed $value, \Closure|string $context="This should never be false")
Definition Utils.php:646
static validateArrayValueType(array $array, \Closure $validator)
Definition Utils.php:596
static validateCallableSignature(callable|CallbackType $signature, callable $subject)
Definition Utils.php:582
static getMachineUniqueId(string $extra="")
Definition Utils.php:200
static stringifyKeys(array $array)
Definition Utils.php:616
static hexdump(string $bin)
Definition Utils.php:338
static getNiceClosureName(\Closure $closure)
Definition Utils.php:124
static getOS(bool $recalculate=false)
Definition Utils.php:276
static currentTrace(int $skipFrames=0)
Definition Utils.php:513
static printable(mixed $str)
Definition Utils.php:353
static printableTraceWithMetadata(array $rawTrace, int $maxStringLength=80)
Definition Utils.php:494
static testValidInstance(string $className, string $baseName)
Definition Utils.php:550
static getOpcacheJitMode()
Definition Utils.php:680
static getNiceClassName(object $obj)
Definition Utils.php:154
static cloneCallback()
Definition Utils.php:172
static cloneObjectArray(array $array)
Definition Utils.php:188
static getRandomFloat()
Definition Utils.php:706
static printableTrace(array $trace, int $maxStringLength=80)
Definition Utils.php:452
static printableCurrentTrace(int $skipFrames=0)
Definition Utils.php:530
static promoteKeys(array $array)
Definition Utils.php:630