PocketMine-MP/src/permission/PermissionManager.php
Dylan K. Taylor 0093732d49
PermissionManager: fixed non-reentrant-safe permission unsubscribing
during unset(), the destructors for other objects with cyclic references can get triggered, resulting in the functions being reentered before the count() call. This leads to a crash because the offset no longer exists.
Instead, we check if only the given PermissibleInternal is present, and clean everything up with a single unset instead of two.
This could also have been solved by adding extra isset() checks before checking the counts, but this way seemed more elegant.

This is similar to an issue with AsyncTask thread-local storage a few months ago, which was also caused by GC reentrancy.

closes #6119
2023-11-01 16:13:28 +00:00

112 lines
3.0 KiB
PHP

<?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\permission;
use function count;
use function spl_object_id;
class PermissionManager{
private static ?self $instance = null;
public static function getInstance() : PermissionManager{
if(self::$instance === null){
self::$instance = new self();
}
return self::$instance;
}
/** @var Permission[] */
protected array $permissions = [];
/** @var PermissibleInternal[][] */
protected array $permSubs = [];
public function getPermission(string $name) : ?Permission{
return $this->permissions[$name] ?? null;
}
public function addPermission(Permission $permission) : bool{
if(!isset($this->permissions[$permission->getName()])){
$this->permissions[$permission->getName()] = $permission;
return true;
}
return false;
}
public function removePermission(Permission|string $permission) : void{
if($permission instanceof Permission){
unset($this->permissions[$permission->getName()]);
}else{
unset($this->permissions[$permission]);
}
}
public function subscribeToPermission(string $permission, PermissibleInternal $permissible) : void{
if(!isset($this->permSubs[$permission])){
$this->permSubs[$permission] = [];
}
$this->permSubs[$permission][spl_object_id($permissible)] = $permissible;
}
public function unsubscribeFromPermission(string $permission, PermissibleInternal $permissible) : void{
if(isset($this->permSubs[$permission][spl_object_id($permissible)])){
if(count($this->permSubs[$permission]) === 1){
unset($this->permSubs[$permission]);
}else{
unset($this->permSubs[$permission][spl_object_id($permissible)]);
}
}
}
public function unsubscribeFromAllPermissions(PermissibleInternal $permissible) : void{
foreach($this->permSubs as $permission => $subs){
if(count($subs) === 1 && isset($subs[spl_object_id($permissible)])){
unset($this->permSubs[$permission]);
}else{
unset($this->permSubs[$permission][spl_object_id($permissible)]);
}
}
}
/**
* @return PermissibleInternal[]
*/
public function getPermissionSubscriptions(string $permission) : array{
return $this->permSubs[$permission] ?? [];
}
/**
* @return Permission[]
*/
public function getPermissions() : array{
return $this->permissions;
}
public function clearPermissions() : void{
$this->permissions = [];
}
}