From 8d4b712f65aac39d72578956ae29e14e43103d86 Mon Sep 17 00:00:00 2001 From: Jonathan Vollebregt Date: Sun, 22 Dec 2024 17:19:11 +0100 Subject: [PATCH] Parser: Use try/finally to unset recursion stacks --- build/kint.phar | Bin 566762 -> 567175 bytes src/Parser/Parser.php | 242 ++++++++++++++++++------------------ tests/Parser/ParserTest.php | 46 +++++++ 3 files changed, 165 insertions(+), 123 deletions(-) diff --git a/build/kint.phar b/build/kint.phar index 01279bfa73ca41da8157a6270dca711aefa715ef..5f695034d1810963f45565e9c2ed39704910b0a9 100644 GIT binary patch delta 1605 zcmZ8hYfKzf6lUkl%syD2i-qzoX=`netp$wL+5$EKZE06aO@mE9Ols@{DQa6ajZ}=J zp-^^td=_vSmdDa%fwk43e+uyj#+V2a#YU_`6H{Y?MWUdIif3lJ6#Q{#@80wN&iT$S zU!>KHrv*N>Mjk8-38}iCo~ZujlpU|Gp0w6XS`B=J;YllPuSVF5q79?Rj50zS=s{%v zCTvDbJ8i+fi;@}D4Rip3DM`kDCmAu_K-&>GCWr`g(r%P?k%U4gJ*VD8%qNivKRYP` zdtVWfV0F=kmPWE5(?$2|R!R?Rl<_;N#*7kv=whF>E;^*yx0DIt_|--0pfm_Fe(WME z&h}FnQW_~8N~;uyiZ4YI{4WR=oNS~AF#WU;1N$Y)z(gaNk4yuDb3U=W3Ey*p?F6Sn~65s zycU}XrNGzE#7JfYAj>gul*U zMsXBHA*Y_}z23|@?{jncCN~E=5+x^NqJ;sz^FFOdnVWJj(`yis@w*U-ksY#xYi^FQ z_?HlY8*Ylkax}YNa8nADY0k>l5kx(;$zVcW2c_x>jh4wGj&{&S9I0bLeFr@Y`)5WY zZgW-Np%Ro6$w=wsTy834L!g?|s5mZ2nCWKJe7EFiq`oGYk=)509~UL$ zlt>o5+`~x~ck2C=RG}GfbW$#B$jzf%#OIy#xQd$@p&qiUUDvc^adRe+f5uDF#1v zah;lUcZn3bI%78vKaZy!wcW(SqI#}cl422gnQz=V3-%n~5(4!G>k1T^dXn1U4)$;l z!@9Y1r72=KQlFP3)%`;lWK|*QMjBrZly8Ve0mXz-&J~vXWjj zs=nh14t6md1%76U27QH}vQc${chHcZF$})hQ1CZT%AbDzu+#!2Ao3#ev*+vq?k86z zBk&SOn?K4-{I-=Vt$LM*#BrJVGBrR^+K$L*V|LFEaE-Mv@WNf-c}N?iculx#kc##9 z{R#5jW#PEUY@V?)neMGCX^3YrxUS-A4i6C@k&nbj9zHx|d}^`aQ2F|i(|ZQS;&o!L+q}b`3^=6f4WI>U-Ki=pt{L$;F OH;W-5A^CZQdH(^-_dn?X delta 1319 zcmZ8hYfPJE6z=z(uV3i}`jxFPF1<(`f@2J)(c!kpWt$3YV^hpB9jTWr#yPhc6EVWZ zBoX}qlyZ4887-v+3gyzpL}G~Z7X9I+Bxp27V`fOaZc+s{G%oXeZ79orylv0>zUMj5 zd!F;ObvEz!&+{T5nUr^`HJav&MY-yC*U^{%Xqhv$&Y1*W;`E$}wzeP|6?Cw-&M&k_xojS4chou~X;-|Fnw%9uG%JFvNr8gF_qhX#~Ks1!fELPjdw zZh+E5*+}UltFRqs>*WlDtz^P43*tJAw+VWfy=1}bUUHz)%avZ;Nf~JMQa#-JxkoEr zZpwWtVmgX_{G5v|Ol!A~FF)&}LbTWE^zf?ThdwI66(1c&^#IkNZ-91Tet@3BmxH83 zt)JFmY=|;(+RyOuJ)fU);P_28U?E6%B=-LGbDCpGHluEkAJ_bnAmQ8~m8hoSk@O1t>Qmnj=VMIC+fcgmcqX8aWsU zF*R1R9I?C+>=O$M|N(S{_z-N}10-W=wxWe+o#RTKq}4f9Z~ z4GUl48iU*ew_VcWgM-|}B{fYs$H)aEt94@`k`N9twWblqi_;B^F!4Cl+G$#d;bTAX zJ6vbELujHF6TEYt>zc1q z7D_xkL*I?jzTQJD7Mo^04u9Oq)##_4%ef4l4r2_k}eyw z6YTNeB;AEe-wPS&+ao%#w~yUfGsR|Lljv}2if%{SWl6zAa_@O7qqv@vxTT7e1^ER^ zgC)Wf)!!%?@zx%ejlETkV1#qoyXhFW^4~z!w>ky2xw_QWVyViSV`P(UJpSU;pHpKZ{pBUbrxGz%*A_dB5eepX(MoH_UrRe+@r9ed9<;!|txk b>`fKNW|m%z_MXv?l+{TZjpo6My%qlhN&@WE diff --git a/src/Parser/Parser.php b/src/Parser/Parser.php index b36c9cf36..a305a8990 100644 --- a/src/Parser/Parser.php +++ b/src/Parser/Parser.php @@ -265,45 +265,45 @@ private function parseArray(array &$var, ContextInterface $c): AbstractValue return $this->applyPluginsComplete($var, $array, self::TRIGGER_RECURSION); } - $this->array_ref_stack[$parentRef] = true; + try { + $this->array_ref_stack[$parentRef] = true; - $cdepth = $c->getDepth(); - $ap = $c->getAccessPath(); + $cdepth = $c->getDepth(); + $ap = $c->getAccessPath(); - if ($size > 0 && $this->depth_limit && $cdepth >= $this->depth_limit) { - $array = new ArrayValue($c, $size, $contents); - $array->flags |= AbstractValue::FLAG_DEPTH_LIMIT; + if ($size > 0 && $this->depth_limit && $cdepth >= $this->depth_limit) { + $array = new ArrayValue($c, $size, $contents); + $array->flags |= AbstractValue::FLAG_DEPTH_LIMIT; - $array = $this->applyPluginsComplete($var, $array, self::TRIGGER_DEPTH_LIMIT); + $array = $this->applyPluginsComplete($var, $array, self::TRIGGER_DEPTH_LIMIT); - unset($this->array_ref_stack[$parentRef]); + return $array; + } - return $array; - } + foreach ($var as $key => $_) { + $child = new ArrayContext($key); + $child->depth = $cdepth + 1; + $child->reference = null !== ReflectionReference::fromArrayElement($var, $key); - foreach ($var as $key => $_) { - $child = new ArrayContext($key); - $child->depth = $cdepth + 1; - $child->reference = null !== ReflectionReference::fromArrayElement($var, $key); + if (null !== $ap) { + $child->access_path = $ap.'['.\var_export($key, true).']'; + } - if (null !== $ap) { - $child->access_path = $ap.'['.\var_export($key, true).']'; + $contents[$key] = $this->parse($var[$key], $child); } - $contents[$key] = $this->parse($var[$key], $child); - } - - $array = new ArrayValue($c, $size, $contents); - - if ($contents) { - $array->addRepresentation(new ContainerRepresentation('Contents', $contents, null, true)); - } + $array = new ArrayValue($c, $size, $contents); - $array = $this->applyPluginsComplete($var, $array, self::TRIGGER_SUCCESS); + if ($contents) { + $array->addRepresentation(new ContainerRepresentation('Contents', $contents, null, true)); + } - unset($this->array_ref_stack[$parentRef]); + $array = $this->applyPluginsComplete($var, $array, self::TRIGGER_SUCCESS); - return $array; + return $array; + } finally { + unset($this->array_ref_stack[$parentRef]); + } } /** @@ -373,130 +373,126 @@ private function parseObject(object &$var, ContextInterface $c): AbstractValue return $this->applyPluginsComplete($var, $object, self::TRIGGER_RECURSION); } + try { + $this->object_hashes[$hash] = true; - $this->object_hashes[$hash] = true; - - $cdepth = $c->getDepth(); - $ap = $c->getAccessPath(); - - if ($this->depth_limit && $cdepth >= $this->depth_limit) { - $object = new InstanceValue($c, $classname, $hash, \spl_object_id($var)); - $object->flags |= AbstractValue::FLAG_DEPTH_LIMIT; - - $object = $this->applyPluginsComplete($var, $object, self::TRIGGER_DEPTH_LIMIT); - - unset($this->object_hashes[$hash]); - - return $object; - } + $cdepth = $c->getDepth(); + $ap = $c->getAccessPath(); - if (KINT_PHP81) { - $props = $this->getPropsOrdered(new ReflectionObject($var)); - } else { - $props = $this->getPropsOrderedOld(new ReflectionObject($var)); // @codeCoverageIgnore - } + if ($this->depth_limit && $cdepth >= $this->depth_limit) { + $object = new InstanceValue($c, $classname, $hash, \spl_object_id($var)); + $object->flags |= AbstractValue::FLAG_DEPTH_LIMIT; - $values = (array) $var; - $properties = []; - - foreach ($props as $rprop) { - $rprop->setAccessible(true); - $name = $rprop->getName(); - - // Casting object to array: - // private properties show in the form "\0$owner_class_name\0$property_name"; - // protected properties show in the form "\0*\0$property_name"; - // public properties show in the form "$property_name"; - // http://www.php.net/manual/en/language.types.array.php#language.types.array.casting - $key = $name; - if ($rprop->isProtected()) { - $key = "\0*\0".$name; - } elseif ($rprop->isPrivate()) { - $key = "\0".$rprop->getDeclaringClass()->getName()."\0".$name; + return $this->applyPluginsComplete($var, $object, self::TRIGGER_DEPTH_LIMIT); } - $initialized = \array_key_exists($key, $values); - if ($key === (string) (int) $key) { - $key = (int) $key; + + if (KINT_PHP81) { + $props = $this->getPropsOrdered(new ReflectionObject($var)); + } else { + $props = $this->getPropsOrderedOld(new ReflectionObject($var)); // @codeCoverageIgnore } - if ($rprop->isDefault()) { - $child = new PropertyContext( - $name, - $rprop->getDeclaringClass()->getName(), - ClassDeclaredContext::ACCESS_PUBLIC - ); + $values = (array) $var; + $properties = []; - $child->readonly = KINT_PHP81 && $rprop->isReadOnly(); + foreach ($props as $rprop) { + $rprop->setAccessible(true); + $name = $rprop->getName(); + // Casting object to array: + // private properties show in the form "\0$owner_class_name\0$property_name"; + // protected properties show in the form "\0*\0$property_name"; + // public properties show in the form "$property_name"; + // http://www.php.net/manual/en/language.types.array.php#language.types.array.casting + $key = $name; if ($rprop->isProtected()) { - $child->access = ClassDeclaredContext::ACCESS_PROTECTED; + $key = "\0*\0".$name; } elseif ($rprop->isPrivate()) { - $child->access = ClassDeclaredContext::ACCESS_PRIVATE; + $key = "\0".$rprop->getDeclaringClass()->getName()."\0".$name; + } + $initialized = \array_key_exists($key, $values); + if ($key === (string) (int) $key) { + $key = (int) $key; } - if (KINT_PHP84) { - if ($rprop->isProtectedSet()) { - $child->access_set = ClassDeclaredContext::ACCESS_PROTECTED; - } elseif ($rprop->isPrivateSet()) { - $child->access_set = ClassDeclaredContext::ACCESS_PRIVATE; + if ($rprop->isDefault()) { + $child = new PropertyContext( + $name, + $rprop->getDeclaringClass()->getName(), + ClassDeclaredContext::ACCESS_PUBLIC + ); + + $child->readonly = KINT_PHP81 && $rprop->isReadOnly(); + + if ($rprop->isProtected()) { + $child->access = ClassDeclaredContext::ACCESS_PROTECTED; + } elseif ($rprop->isPrivate()) { + $child->access = ClassDeclaredContext::ACCESS_PRIVATE; } - $hooks = $rprop->getHooks(); - if (isset($hooks['get'])) { - $child->hooks |= PropertyContext::HOOK_GET; - if ($hooks['get']->returnsReference()) { - $child->hooks |= PropertyContext::HOOK_GET_REF; + if (KINT_PHP84) { + if ($rprop->isProtectedSet()) { + $child->access_set = ClassDeclaredContext::ACCESS_PROTECTED; + } elseif ($rprop->isPrivateSet()) { + $child->access_set = ClassDeclaredContext::ACCESS_PRIVATE; } - } - if (isset($hooks['set'])) { - $child->hooks |= PropertyContext::HOOK_SET; - - $child->hook_set_type = (string) $rprop->getSettableType(); - if ($child->hook_set_type !== (string) $rprop->getType()) { - $child->hooks |= PropertyContext::HOOK_SET_TYPE; - } elseif ('' === $child->hook_set_type) { - $child->hook_set_type = null; + + $hooks = $rprop->getHooks(); + if (isset($hooks['get'])) { + $child->hooks |= PropertyContext::HOOK_GET; + if ($hooks['get']->returnsReference()) { + $child->hooks |= PropertyContext::HOOK_GET_REF; + } + } + if (isset($hooks['set'])) { + $child->hooks |= PropertyContext::HOOK_SET; + + $child->hook_set_type = (string) $rprop->getSettableType(); + if ($child->hook_set_type !== (string) $rprop->getType()) { + $child->hooks |= PropertyContext::HOOK_SET_TYPE; + } elseif ('' === $child->hook_set_type) { + $child->hook_set_type = null; + } } } + } else { + $child = new ClassOwnedContext($name, $rprop->getDeclaringClass()->getName()); } - } else { - $child = new ClassOwnedContext($name, $rprop->getDeclaringClass()->getName()); - } - $child->reference = $initialized && null !== ReflectionReference::fromArrayElement($values, $key); - $child->depth = $cdepth + 1; + $child->reference = $initialized && null !== ReflectionReference::fromArrayElement($values, $key); + $child->depth = $cdepth + 1; + + if (null !== $ap && $child->isAccessible($this->caller_class)) { + /** @psalm-var string $child->name */ + if (Utils::isValidPhpName($child->name)) { + $child->access_path = $ap.'->'.$child->name; + } else { + $child->access_path = $ap.'->{'.\var_export($child->name, true).'}'; + } + } - if (null !== $ap && $child->isAccessible($this->caller_class)) { - /** @psalm-var string $child->name */ - if (Utils::isValidPhpName($child->name)) { - $child->access_path = $ap.'->'.$child->name; + if (KINT_PHP84 && $rprop->isVirtual()) { + $properties[] = new VirtualValue($child); + } elseif (!$initialized) { + $properties[] = new UninitializedValue($child); } else { - $child->access_path = $ap.'->{'.\var_export($child->name, true).'}'; + $properties[] = $this->parse($values[$key], $child); } } - if (KINT_PHP84 && $rprop->isVirtual()) { - $properties[] = new VirtualValue($child); - } elseif (!$initialized) { - $properties[] = new UninitializedValue($child); - } else { - $properties[] = $this->parse($values[$key], $child); + $object = new InstanceValue($c, $classname, $hash, \spl_object_id($var)); + if ($props) { + $object->setChildren($properties); } - } - $object = new InstanceValue($c, $classname, $hash, \spl_object_id($var)); - if ($props) { - $object->setChildren($properties); - } + if ($properties) { + $object->addRepresentation(new ContainerRepresentation('Properties', $properties)); + } - if ($properties) { - $object->addRepresentation(new ContainerRepresentation('Properties', $properties)); + return $this->applyPluginsComplete($var, $object, self::TRIGGER_SUCCESS); + } finally { + unset($this->object_hashes[$hash]); } - - $object = $this->applyPluginsComplete($var, $object, self::TRIGGER_SUCCESS); - unset($this->object_hashes[$hash]); - - return $object; } /** diff --git a/tests/Parser/ParserTest.php b/tests/Parser/ParserTest.php index a987c678b..cfba31be5 100644 --- a/tests/Parser/ParserTest.php +++ b/tests/Parser/ParserTest.php @@ -1425,4 +1425,50 @@ function () { $this->fail("Didn't get warning"); } + + public function testPluginsExceptionCleansUpRecursionMarkers() + { + $p = new Parser(); + $b = new BaseContext('$v'); + $v = new stdClass(); + $v->v = $v; + + $pl = new ProxyPlugin( + ['object', 'array'], + Parser::TRIGGER_COMPLETE, + static function (&$var, AbstractValue $v) { + if ($v->getContext()->getDepth()) { + throw new Exception(); + } + } + ); + $p->addPlugin($pl); + + try { + $o = $p->parse($v, clone $b); + } catch (Exception $e) { + } + + $p->clearPlugins(); + + $o = $p->parse($v, clone $b); + $this->assertEquals(false, $o->flags & AbstractValue::FLAG_RECURSION); + $this->assertEquals(true, $o->getChildren()[0]->flags & AbstractValue::FLAG_RECURSION); + + $v = []; + $v[] = &$v; + + $p->addPlugin($pl); + + try { + $o = $p->parse($v, clone $b); + } catch (Exception $e) { + } + + $p->clearPlugins(); + + $o = $p->parse($v, clone $b); + $this->assertEquals(false, $o->flags & AbstractValue::FLAG_RECURSION); + $this->assertEquals(true, $o->getContents()[0]->flags & AbstractValue::FLAG_RECURSION); + } }