From 50a8d5719abfcc87300ea0f67095210dae4c2062 Mon Sep 17 00:00:00 2001 From: Greg Anderson Date: Fri, 5 Apr 2024 14:04:17 -0700 Subject: [PATCH] Avoid infinite loops during command info parsing. (#309) * Note that we have parsed everything as soon as we enter the "parse" method, to avoid infinite loops. * Make parsing operation safer --------- Co-authored-by: Greg Anderson --- src/Parser/CommandInfo.php | 45 ++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/Parser/CommandInfo.php b/src/Parser/CommandInfo.php index 60a700a..2f24515 100644 --- a/src/Parser/CommandInfo.php +++ b/src/Parser/CommandInfo.php @@ -31,9 +31,15 @@ class CommandInfo /** * @var boolean * @var string - */ + */ protected $docBlockIsParsed = false; + /** + * @var boolean + * @var string + */ + protected $parsingInProgress = false; + /** * @var string */ @@ -164,6 +170,9 @@ protected function constructFromClassAndMethod($classNameOrInstance, $methodName $this->simpleOptionParametersAllowed = empty($optionsFromParameters); $this->options = new DefaultsWithDescriptions($optionsFromParameters, false); $this->arguments = $this->determineAgumentClassifications(); + + // Construct the object from docblock annotations or php attributes + $this->parseDocBlock(); } /** @@ -183,7 +192,7 @@ public function getMethodName() */ public function getName() { - $this->parseDocBlock(); + // getName() is the only attribute that may be used during parsing. return $this->name; } @@ -227,13 +236,13 @@ public function getParameterMap() public function getReturnType() { - $this->parseDocBlock(); + $this->requireConsistentState(); return $this->returnType; } public function getInjectedClasses() { - $this->parseDocBlock(); + $this->requireConsistentState(); return $this->injectedClasses; } @@ -258,7 +267,7 @@ public function setReturnType($returnType) */ public function getRawAnnotations() { - $this->parseDocBlock(); + $this->requireConsistentState(); return $this->otherAnnotations; } @@ -336,7 +345,7 @@ public function getAnnotation($name) */ public function hasAnnotation($annotation) { - $this->parseDocBlock(); + $this->requireConsistentState(); return isset($this->otherAnnotations[$annotation]); } @@ -369,7 +378,7 @@ public function removeAnnotation($name) */ public function getDescription() { - $this->parseDocBlock(); + $this->requireConsistentState(); return $this->description; } @@ -397,7 +406,7 @@ public function hasHelp() */ public function getHelp() { - $this->parseDocBlock(); + $this->requireConsistentState(); return $this->help; } /** @@ -417,7 +426,7 @@ public function setHelp($help) */ public function getAliases() { - $this->parseDocBlock(); + $this->requireConsistentState(); return $this->aliases; } @@ -441,7 +450,7 @@ public function setAliases($aliases) */ public function getHidden() { - $this->parseDocBlock(); + $this->requireConsistentState(); return $this->hasAnnotation('hidden'); } @@ -465,7 +474,7 @@ public function setHidden($hidden) */ public function getExampleUsages() { - $this->parseDocBlock(); + $this->requireConsistentState(); return $this->exampleUsage; } @@ -865,6 +874,16 @@ protected function camelToSnake($camel, $splitter = '_') return strtolower($camel); } + /** + * Guard against invalid usage of CommandInfo during parsing. + */ + protected function requireConsistentState() + { + if ($this->parsingInProgress == true) { + throw new \Exception("Cannot use CommandInfo object while it is in an inconsistant state!"); + } + } + /** * Parse the docBlock comment for this command, and set the * fields of this class with the data thereby obtained. @@ -872,10 +891,12 @@ protected function camelToSnake($camel, $splitter = '_') protected function parseDocBlock() { if (!$this->docBlockIsParsed) { + $this->docBlockIsParsed = true; + $this->parsingInProgress = true; // The parse function will insert data from the provided method // into this object, using our accessors. CommandDocBlockParserFactory::parse($this, $this->reflection); - $this->docBlockIsParsed = true; + $this->parsingInProgress = false; // Use method's return type if @return is not present. if ($this->reflection->hasReturnType() && !$this->getReturnType()) { $type = $this->reflection->getReturnType();