Skip to content

Commit

Permalink
Avoid infinite loops during command info parsing. (#309)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
greg-1-anderson and Greg Anderson authored Apr 5, 2024
1 parent f5e90aa commit 50a8d57
Showing 1 changed file with 33 additions and 12 deletions.
45 changes: 33 additions & 12 deletions src/Parser/CommandInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,15 @@ class CommandInfo
/**
* @var boolean
* @var string
*/
*/
protected $docBlockIsParsed = false;

/**
* @var boolean
* @var string
*/
protected $parsingInProgress = false;

/**
* @var string
*/
Expand Down Expand Up @@ -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();
}

/**
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}

Expand All @@ -258,7 +267,7 @@ public function setReturnType($returnType)
*/
public function getRawAnnotations()
{
$this->parseDocBlock();
$this->requireConsistentState();
return $this->otherAnnotations;
}

Expand Down Expand Up @@ -336,7 +345,7 @@ public function getAnnotation($name)
*/
public function hasAnnotation($annotation)
{
$this->parseDocBlock();
$this->requireConsistentState();
return isset($this->otherAnnotations[$annotation]);
}

Expand Down Expand Up @@ -369,7 +378,7 @@ public function removeAnnotation($name)
*/
public function getDescription()
{
$this->parseDocBlock();
$this->requireConsistentState();
return $this->description;
}

Expand Down Expand Up @@ -397,7 +406,7 @@ public function hasHelp()
*/
public function getHelp()
{
$this->parseDocBlock();
$this->requireConsistentState();
return $this->help;
}
/**
Expand All @@ -417,7 +426,7 @@ public function setHelp($help)
*/
public function getAliases()
{
$this->parseDocBlock();
$this->requireConsistentState();
return $this->aliases;
}

Expand All @@ -441,7 +450,7 @@ public function setAliases($aliases)
*/
public function getHidden()
{
$this->parseDocBlock();
$this->requireConsistentState();
return $this->hasAnnotation('hidden');
}

Expand All @@ -465,7 +474,7 @@ public function setHidden($hidden)
*/
public function getExampleUsages()
{
$this->parseDocBlock();
$this->requireConsistentState();
return $this->exampleUsage;
}

Expand Down Expand Up @@ -865,17 +874,29 @@ 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.
*/
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();
Expand Down

0 comments on commit 50a8d57

Please sign in to comment.