Last active
October 18, 2024 03:00
-
-
Save DanielEScherzer/1ca7856f2c25dbf30ffda1f9f529ffdd to your computer and use it in GitHub Desktop.
Invoking methods on uninitialized objects
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
$skip = [ | |
"SensitiveParameterValue::getValue", | |
"SQLite3Result::__construct", | |
"Dom\\Node::__construct", | |
"Dom\\NamespaceInfo::__construct", | |
"DOMDocument::registerNodeClass", | |
"ArrayObject::setIteratorClass", | |
"ArrayIterator::seek", | |
"SplFileInfo::_bad_state_ex", | |
"GlobIterator::count", | |
"SplPriorityQueue::setExtractFlags", | |
"SplObjectStorage::current", | |
"SplObjectStorage::seek", | |
"php_user_filter::filter", | |
"ReflectionMethod::__construct", | |
"ReflectionMethod::createFromMethodName", | |
"ReflectionClassConstant::__construct", | |
"ReflectionExtension::__construct", | |
"ReflectionZendExtension::__construct", | |
"ReflectionAttribute::__construct", | |
"ReflectionEnum::__construct", | |
"ReflectionEnumUnitCase::__construct", | |
"ReflectionEnumBackedCase::__construct", | |
"Phar::__construct", | |
"Phar::mount", | |
"Phar::mungServer", | |
"PharData::__construct", | |
"PharData::mount", | |
"PharData::mungServer", | |
"PharFileInfo::__construct", | |
"SimpleXMLElement::__construct", | |
"XMLReader::getParserProperty", | |
"XMLReader::fromStream", | |
"XMLWriter::toStream", | |
// Extensions not enabled by default | |
"DOMImplementation::getFeature", | |
"IntlBreakIterator::getIterator", | |
"IntlRuleBasedBreakIterator::__construct", | |
"mysqli::connect", | |
"mysqli::poll", | |
"mysqli_stmt::bind_result", | |
"SNMP::setSecurity", | |
"SoapClient::__construct", | |
"SoapServer::__construct", | |
"SoapServer::setClass", | |
"tidy::getOpt", | |
"tidy::getOptDoc", | |
"_ZendTestClass::returnsThrowable", | |
"_ZendTestChildClass::returnsThrowable", | |
"ZendTestForbidDynamicCall::call", | |
"ZendTestForbidDynamicCall::callStatic", | |
"ZipArchive::addPattern", | |
// Types not in reflection | |
"IntlDateFormatter::formatObject", | |
// 16215 (type not in reflection) | |
"RecursiveTreeIterator::__construct", | |
// 16216 (type not in reflection) | |
"ReflectionParameter::__construct", | |
// 16217 (seg fault) | |
"SplFileObject::fputcsv", | |
// 16218 (type not in reflection) | |
"PhpToken::is", | |
]; | |
$skip = array_fill_keys( $skip, true ); | |
$messages = [ | |
"Typed property access" => "/Typed property \S+ must not be accessed before initialization/", | |
"Unserialize offset" => "/Error at offset 0 of 3 bytes/", | |
"Unserialize data" => "/Incomplete or ill-typed serialization data/", | |
"No __clone" => "/Cannot clone object using __clone\(\)/", | |
"Date* not initialized" => "/Object of type Date\S* has not been correctly initialized/", | |
"Date* serialization data" => "/Invalid serialization data for Date\S* object/", | |
"Date* parsing" => "/Unknown or bad (timezone|format)/", | |
"SQLite3* initialised" => "/The SQLite3\S* object has not been correctly initialised/", | |
"Dom fetch" => "/Couldn't fetch (DOM|Dom\\\\)\S+/", | |
"Dom no serialize" => "/(Uns|S)erialization of '(DOM|Dom\\\\)\S+' is not allowed/", | |
"Dom xpath" => "/Invalid XPath Context/", | |
"finfo invalid" => "/Invalid finfo object/", | |
"RecursiveIteratorIterator invalid" => "/The object is in an invalid state/", | |
"Bad regex" => "/Delimiter must not be alphanumeric/", | |
"EmptyIterator" => "/Accessing the \S+ of an EmptyIterator/", | |
"SplFileInfo initialized" => "/Object not initialized/", | |
"Directories" => "/Failed to open directory:/", | |
"Spl empty" => "/Can't \S+ (from|at) an empty (datastructure|heap)/", | |
"SplDoublyLinkedList offset" => "/Argument #1 \(\\\$index\) is out of range/", | |
"Null offset" => "/Cannot access offset of type null/", | |
"SplObjectStorage offset" => "/SplObjectStorage::offset\S+ Argument \#1 \(\\\$object\) must be of type object/", | |
"MultipleIterator" => "/Called (key|current)\(\) on an invalid iterator/", | |
"No session" => "/Session is not active/", | |
"PDO data source" => "/Argument \#\d \(\\\$\S+ must be a valid data source name/", | |
"PDO* uninitialized" => "/(PDO|Pdo\\\\)\S* object is uninitialized/", | |
"Directory handle" => "/Unable to find my handle property/", | |
"Reflection failure" => "/Failed to retrieve the reflection object/", | |
"Phar* uninitialized" => "/Cannot call method on an uninitialized Phar\S* object/", | |
"Phar halt compiler" => "/__HALT_COMPILER\(\); must be declared in a phar/", | |
"Phar corruption" => "/internal corruption of phar/", | |
"SimpleXMLElement uninitialized" => "/SimpleXMLElement is not properly initialized/", | |
"SimpleXMLElement iterator" => "/Iterator not initialized or already consumed/", | |
"XMLReader not loaded" => "/Data must be loaded before (reading|expanding)/", | |
"XMLReader not loaded 2" => "/Schema must be set prior to reading/", | |
"XMLReader not loaded 3" => "/Cannot access parser properties before loading data/", | |
"XMLWriter uninitialized" => "/Invalid or uninitialized XMLWriter object/", | |
// Non-default extensions | |
"PDO uninitialized" => "/PDO object is not initialized, constructor was not called/", | |
"NumberFormatter locales" => "/Argument #1 \\(\\\$locale\) \S+ is invalid/", | |
"Intl construct" => "/Found unconstructed \S+/", | |
"Intl no new" => "/An object of this type cannot be created with the new operator/", | |
"mysqli* closed" => "/mysqli\S* object is already closed/", | |
"mysqli init" => "/mysqli object is not fully initialized/", | |
"SNMP non-empty" => "/Array of object IDs must not be empty/", | |
"SoapClient uri" => "/Error finding \"uri\" property/", | |
"SoapServer fetch" => "/Cannot fetch SoapServer object/", | |
"Tidy init" => "/tidy object is not initialized/", | |
"XSLT invalid XML" => "/Argument #1 \\(\\\$\S+\\) must be a valid XML node/", | |
"Zip not initialized" => "/Invalid or uninitialized Zip object/", | |
]; | |
// Only for non-reflection running | |
// $messages = [ | |
// "Typed property access" => "/Typed property \S+ must not be accessed before initialization/", | |
// "Date* not initialized" => "/Object of type Date\S* has not been correctly initialized/", | |
// "Date* serialization data" => "/Invalid serialization data for Date\S* object/", | |
// "Date* parsing" => "/Unknown or bad (timezone|format)/", | |
// "SQLite3* initialised" => "/The SQLite3\S* object has not been correctly initialised/", | |
// "Dom fetch" => "/Couldn't fetch (DOM|Dom\\\\)\S+/", | |
// "Dom no serialize" => "/(Uns|S)erialization of '(DOM|Dom\\\\)\S+' is not allowed/", | |
// "Dom xpath" => "/Invalid XPath Context/", | |
// "finfo invalid" => "/Invalid finfo object/", | |
// "iterator not initialized" => "/The \S+Iterator instance wasn't initialized/", | |
// "IteratorIterator invalid" => "/The object is in an invalid state/", | |
// "Bad regex" => "/Delimiter must not be alphanumeric/", | |
// "EmptyIterator" => "/Accessing the \S+ of an EmptyIterator/", | |
// "Unserialize offset" => "/Error at offset 0 of 3 bytes/", | |
// "Unserialize data" => "/Incomplete or ill-typed serialization data/", | |
// "SplFileInfo initialized" => "/Object not initialized/", | |
// "Directories" => "/Failed to open directory:/", | |
// "GlobIterator parent" => "/The parent constructor was not called/", | |
// "Spl empty" => "/Can't \S+ (from|at) an empty (datastructure|heap)/", | |
// "SplDoublyLinkedList offset" => "/Argument #1 \(\\\$index\) is out of range/", | |
// "Null offset" => "/Cannot access offset of type null/", | |
// "SplObjectStorage offset" => "/SplObjectStorage::offset\S+ Argument \#1 \(\\\$object\) must be of type object/", | |
// "MultipleIterator" => "/Called (key|current)\(\) on an invalid iterator/", | |
// "No session" => "/Session is not active/", | |
// "PDO data source" => "/Argument \#\d \(\\\$\S+ must be a valid data source name/", | |
// "PDO* uninitialized" => "/(PDO|Pdo\\\\)\S* object is uninitialized/", | |
// "Directory handle" => "/Unable to find my handle property/", | |
// "Reflection failure" => "/Failed to retrieve the reflection object/", | |
// "Phar* uninitialized" => "/Cannot call method on an uninitialized Phar\S* object/", | |
// "Phar halt compiler" => "/__HALT_COMPILER\(\); must be declared in a phar/", | |
// "Phar corruption" => "/internal corruption of phar/", | |
// "SimpleXMLElement uninitialized" => "/SimpleXMLElement is not properly initialized/", | |
// "SimpleXMLElement iterator" => "/Iterator not initialized or already consumed/", | |
// "XMLReader not loaded" => "/Data must be loaded before (reading|expanding)/", | |
// "XMLReader not loaded 2" => "/Schema must be set prior to reading/", | |
// "XMLReader not loaded 3" => "/Cannot access parser properties before loading data/", | |
// "XMLWriter uninitialized" => "/Invalid or uninitialized XMLWriter object/", | |
// ]; | |
function getForType( $type ) { | |
if ( !$type || $type->allowsNull() ) { | |
return null; | |
} | |
if ( $type instanceof ReflectionIntersectionType ) { | |
throw new Exception( "ReflectionIntersectionType" ); | |
} | |
if ( $type instanceof ReflectionUnionType ) { | |
$type = $type->getTypes()[0]; | |
} | |
switch ( $type->getName() ) { | |
case "array": return []; | |
case "string": return "foo"; | |
case "int": return 1; | |
case "float": return 123.45; | |
case "callable": return fn () => true; | |
case "bool": return true; | |
case "object": return (object)[ "foo" => "bar" ]; | |
case "stdclass": return (object)[ "foo" => "bar" ]; | |
case "Closure": return array_merge(...); | |
case "Traversable": return new RecursiveArrayIterator( [] ); | |
case "Iterator": return new RecursiveArrayIterator( [] ); | |
case "RecursiveIterator": return new RecursiveArrayIterator( [] ); | |
case "SplObjectStorage": return new SplObjectStorage(); | |
case "SQLite3": return new SQLite3(":memory:"); | |
// extension: Date | |
case "DateTimeImmutable": return new DateTimeImmutable(); | |
case "DateTimeInterface": return new DateTimeImmutable(); | |
case "DateInterval": return new DateInterval("P1D"); | |
case "DateTimeZone": return new DateTimeZone("UTC"); | |
case "DateTime": return new DateTime(); | |
// extension: DOM | |
case "DOMNode": return new DOMText("foo"); | |
case "Dom\\Node": return Dom\HTMLDocument::createEmpty(); | |
case "DOMAttr": return new DOMAttr("foo", "bar"); | |
case "DOMElement": return new DOMElement("br"); | |
case "Dom\\Attr": return Dom\HTMLDocument::createEmpty()->createAttribute("foo"); | |
case "Dom\\AdjacentPosition": return Dom\AdjacentPosition::BeforeBegin; | |
case "Dom\\Element": return Dom\HTMLDocument::createEmpty()->createElement("br"); | |
case "DOMDocument": return new DOMDocument(); | |
// extension: Reflection | |
case "ReflectionClass": return new ReflectionClass( UnitEnum::class ); | |
case "PropertyHookType": return PropertyHookType::Get; | |
// extension: Inttl | |
case "IntlTimeZone": return IntlTimeZone::createDefault(); | |
case "IntlCalendar": return IntlCalendar::createInstance(); | |
// extension: mysqli | |
case "mysqli": return new mysqli(); | |
// extension: Soap | |
case "SoapHeader": return new SoapHeader("foo", "bar"); | |
} | |
echo "\n"; | |
var_dump( $type->getName() ); | |
throw new Exception( "Not handled" ); | |
} | |
function getParams( $method ) { | |
$result = []; | |
$params = $method->getParameters(); | |
$params = array_slice( $params, 0, $method->getNumberOfRequiredParameters() ); | |
foreach ( $params as $p ) { | |
$result[] = getForType( $p->getType() ); | |
} | |
return $result; | |
} | |
function doRun( ReflectionMethod $method, object $instance, array $params ) { | |
$method->invokeArgs( $instance, getParams( $method ) ); | |
return; | |
if ( $method->isPublic() ) { | |
$name = $method->name; | |
$instance->$name( ...$params ); | |
} | |
} | |
function runForMethod( $method, $k, $ref ) { | |
global $skip, $messages; | |
echo "\tmethod " . $method->name; | |
if ( array_key_exists( $ref->name . "::" . $method->name, $skip ) ) { | |
echo "- skipping per skip list\n"; | |
return; | |
} | |
$instance = $ref->newInstanceWithoutConstructor(); | |
try { | |
doRun( $method, $instance, getParams( $method ) ); | |
} catch ( Throwable $e ) { | |
foreach ( $messages as $note => $regex ) { | |
if ( preg_match( $regex, $e->getMessage() ) ) { | |
echo ": $note\n"; | |
return; | |
} | |
} | |
throw $e; | |
} | |
echo ": success\n"; | |
} | |
function runForClass( $clazz ) { | |
$ref = new ReflectionClass( $clazz ); | |
if ( $ref->implementsInterface( "UnitEnum") ) { | |
echo " enum, skipping\n"; | |
return; | |
} elseif ( $ref->isAbstract() ) { | |
echo " abstract, skipping\n"; | |
return; | |
} | |
try { | |
$ref->newInstanceWithoutConstructor(); | |
} catch ( ReflectionException $e ) { | |
echo " internal final, skipping\n"; | |
return; | |
} | |
$methods = array_filter( | |
$ref->getMethods(), | |
static fn ( $meth ) => $meth->class === $clazz | |
); | |
if ( $methods === [] ) { | |
echo " no methods, skipping\n"; | |
} else { | |
echo "\n"; | |
array_walk( $methods, runForMethod(...), $ref ); | |
} | |
} | |
$classes = get_declared_classes(); | |
$n = 0; | |
foreach ( $classes as $class ) { | |
$n++; | |
echo "Trying for class #$n: $class..."; | |
runForClass( $class ); | |
} | |
echo "Total classes: $n\n"; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment