diff --git a/app/Utils/Stack.php b/app/Utils/Stack.php index 7b123b112..60e6ae4a3 100644 --- a/app/Utils/Stack.php +++ b/app/Utils/Stack.php @@ -2,10 +2,15 @@ namespace App\Utils; +use InvalidArgumentException; + +/** + * @template T + */ class Stack { /** - * @var array + * @var array */ private array $stack = []; @@ -14,18 +19,29 @@ public function size(): int return count($this->stack); } + /** + * @param T $e + * + * @return self + */ public function push(mixed $e): self { $this->stack[] = $e; return $this; } + /** + * @return self + */ public function pop(): self { array_pop($this->stack); return $this; } + /** + * @return T + */ public function top(): mixed { return $this->stack[count($this->stack) - 1]; @@ -36,10 +52,13 @@ public function isEmpty(): bool return count($this->stack) === 0; } + /** + * @return T + */ public function at(int $index): mixed { if ($index < 0 || $index >= count($this->stack)) { - return false; + throw new InvalidArgumentException("Invalid stack index: $index"); } return $this->stack[$index]; } diff --git a/app/cdash/xml_handlers/abstract_xml_handler.php b/app/cdash/xml_handlers/abstract_xml_handler.php index ae636a446..b9cc26af8 100644 --- a/app/cdash/xml_handlers/abstract_xml_handler.php +++ b/app/cdash/xml_handlers/abstract_xml_handler.php @@ -26,6 +26,9 @@ abstract class AbstractXmlHandler extends AbstractSubmissionHandler { + /** + * @var Stack + */ private Stack $stack; protected bool $Append = false; protected Site $Site; @@ -103,11 +106,36 @@ public static function validate(string $path): array return $errors; } - protected function getParent() + protected function getParent(): ?string { + if ($this->stack->size() <= 1) { + return null; + } + return $this->stack->at($this->stack->size() - 2); } + protected function currentPathMatches(string $path): bool + { + $path = explode('.', $path); + + // We can return early if this isn't even the right level of the document + if ($this->stack->size() !== count($path)) { + return false; + } + + for ($i = 0; $i < $this->stack->size(); $i++) { + if ($path[$i] === '*') { // Wildcard matches any string at a given level (but only one level) + continue; + } elseif (strtoupper($path[$i]) === strtoupper((string) $this->stack->at($i))) { // Match the specified string + continue; + } else { + return false; + } + } + return true; + } + protected function getElement() { return $this->stack->top(); diff --git a/app/cdash/xml_handlers/build_handler.php b/app/cdash/xml_handlers/build_handler.php index 4ac46f897..edd52f22e 100644 --- a/app/cdash/xml_handlers/build_handler.php +++ b/app/cdash/xml_handlers/build_handler.php @@ -75,7 +75,7 @@ public function startElement($parser, $name, $attributes): void parent::startElement($parser, $name, $attributes); $factory = $this->getModelFactory(); - if ($name == 'SITE') { + if ($this->currentPathMatches('site')) { $site_name = !empty($attributes['NAME']) ? $attributes['NAME'] : '(empty)'; $this->Site = Site::firstOrCreate(['name' => $site_name], ['name' => $site_name]); @@ -192,8 +192,6 @@ public function startElement($parser, $name, $attributes): void public function endElement($parser, $name): void { - $parent = $this->getParent(); // should be before endElement - parent::endElement($parser, $name); $factory = $this->getModelFactory(); if ($name == 'BUILD') { @@ -302,7 +300,7 @@ public function endElement($parser, $name): void $this->Builds[$this->SubProjectName]->AddError($this->Error); } unset($this->Error); - } elseif ($name == 'LABEL' && $parent == 'LABELS') { + } elseif ($name == 'LABEL' && $this->getParent() === 'LABELS') { if (!empty($this->ErrorSubProjectName)) { $this->SubProjectName = $this->ErrorSubProjectName; } elseif (isset($this->Error) && $this->Error instanceof BuildFailure) { @@ -311,13 +309,14 @@ public function endElement($parser, $name): void $this->Labels[] = $this->Label; } } + + parent::endElement($parser, $name); } public function text($parser, $data) { - $parent = $this->getParent(); $element = $this->getElement(); - if ($parent == 'BUILD') { + if ($this->getParent() === 'BUILD') { switch ($element) { case 'STARTBUILDTIME': $this->StartTimeStamp = $data; @@ -329,7 +328,7 @@ public function text($parser, $data) $this->BuildCommand = htmlspecialchars_decode($data); break; } - } elseif ($parent == 'ACTION') { + } elseif ($this->getParent() === 'ACTION') { switch ($element) { case 'LANGUAGE': $this->Error->Language .= $data; @@ -347,7 +346,7 @@ public function text($parser, $data) $this->Error->OutputType .= $data; break; } - } elseif ($parent == 'COMMAND') { + } elseif ($this->getParent() === 'COMMAND') { switch ($element) { case 'WORKINGDIRECTORY': $this->Error->WorkingDirectory .= $data; @@ -356,7 +355,7 @@ public function text($parser, $data) $this->Error->AddArgument($data); break; } - } elseif ($parent == 'RESULT') { + } elseif ($this->getParent() === 'RESULT') { switch ($element) { case 'STDOUT': $this->Error->StdOutput .= $data; @@ -382,9 +381,9 @@ public function text($parser, $data) $this->Error->PreContext .= $data; } elseif ($element == 'POSTCONTEXT') { $this->Error->PostContext .= $data; - } elseif ($parent == 'SUBPROJECT' && $element == 'LABEL') { + } elseif ($this->getParent() === 'SUBPROJECT' && $element == 'LABEL') { $this->SubProjects[$this->SubProjectName][] = $data; - } elseif ($parent == 'LABELS' && $element == 'LABEL') { + } elseif ($this->getParent() === 'LABELS' && $element == 'LABEL') { // First, check if this label belongs to a SubProject foreach ($this->SubProjects as $subproject => $labels) { if (in_array($data, $labels)) { diff --git a/app/cdash/xml_handlers/configure_handler.php b/app/cdash/xml_handlers/configure_handler.php index ccce2ac67..2c35e5d2e 100644 --- a/app/cdash/xml_handlers/configure_handler.php +++ b/app/cdash/xml_handlers/configure_handler.php @@ -69,7 +69,7 @@ public function startElement($parser, $name, $attributes): void { parent::startElement($parser, $name, $attributes); - if ($name == 'SITE') { + if ($this->currentPathMatches('site')) { $sitename = !empty($attributes['NAME']) ? $attributes['NAME'] : '(empty)'; $this->Site = Site::firstOrCreate(['name' => $sitename], ['name' => $sitename]); @@ -144,10 +144,6 @@ public function startElement($parser, $name, $attributes): void public function endElement($parser, $name): void { - $parent = $this->getParent(); - - parent::endElement($parser, $name); - if ($name == 'CONFIGURE') { $start_time = gmdate(FMT_DATETIME, $this->StartTimeStamp); $end_time = gmdate(FMT_DATETIME, $this->EndTimeStamp); @@ -252,11 +248,13 @@ public function endElement($parser, $name): void // so only need to do this once $build->UpdateParentConfigureNumbers( (int) $this->Configure->NumberOfWarnings, (int) $this->Configure->NumberOfErrors); - } elseif ($name == 'LABEL' && $parent == 'LABELS') { + } elseif ($name === 'LABEL' && $this->getParent() === 'LABELS') { if (isset($this->Configure)) { $this->Configure->AddLabel($this->Label); } } + + parent::endElement($parser, $name); } public function text($parser, $data) diff --git a/app/cdash/xml_handlers/coverage_handler.php b/app/cdash/xml_handlers/coverage_handler.php index 44c6b96e9..b66ae7680 100644 --- a/app/cdash/xml_handlers/coverage_handler.php +++ b/app/cdash/xml_handlers/coverage_handler.php @@ -55,7 +55,7 @@ public function __construct(Project $project) public function startElement($parser, $name, $attributes): void { parent::startElement($parser, $name, $attributes); - if ($name == 'SITE') { + if ($this->currentPathMatches('site')) { $site_name = !empty($attributes['NAME']) ? $attributes['NAME'] : '(empty)'; $this->Site = Site::firstOrCreate(['name' => $site_name], ['name' => $site_name]); @@ -113,8 +113,7 @@ public function startElement($parser, $name, $attributes): void /** End element */ public function endElement($parser, $name): void { - parent::endElement($parser, $name); - if ($name == 'SITE') { + if ($this->currentPathMatches('site')) { $start_time = gmdate(FMT_DATETIME, $this->StartTimeStamp); $end_time = gmdate(FMT_DATETIME, $this->EndTimeStamp); @@ -187,6 +186,7 @@ public function endElement($parser, $name): void $this->Coverage->AddLabel($this->Label); } } + parent::endElement($parser, $name); } /** Text function */ diff --git a/app/cdash/xml_handlers/coverage_junit_handler.php b/app/cdash/xml_handlers/coverage_junit_handler.php index b0cb1bb62..a44c430f2 100644 --- a/app/cdash/xml_handlers/coverage_junit_handler.php +++ b/app/cdash/xml_handlers/coverage_junit_handler.php @@ -50,7 +50,7 @@ public function startElement($parser, $name, $attributes): void { parent::startElement($parser, $name, $attributes); $parent = $this->getParent(); - if ($name == 'SITE') { + if ($this->currentPathMatches('site')) { $site_name = !empty($attributes['NAME']) ? $attributes['NAME'] : '(empty)'; $this->Site = Site::firstOrCreate(['name' => $site_name], ['name' => $site_name]); @@ -134,8 +134,7 @@ public function startElement($parser, $name, $attributes): void /** End element */ public function endElement($parser, $name): void { - parent::endElement($parser, $name); - if ($name == 'SITE') { + if ($this->currentPathMatches('site')) { $start_time = gmdate(FMT_DATETIME, $this->StartTimeStamp); $end_time = gmdate(FMT_DATETIME, $this->EndTimeStamp); @@ -173,6 +172,8 @@ public function endElement($parser, $name): void $this->Coverage->AddLabel($this->Label); } } + + parent::endElement($parser, $name); } /** Text function */ diff --git a/app/cdash/xml_handlers/coverage_log_handler.php b/app/cdash/xml_handlers/coverage_log_handler.php index 64f633422..c71f5f59d 100644 --- a/app/cdash/xml_handlers/coverage_log_handler.php +++ b/app/cdash/xml_handlers/coverage_log_handler.php @@ -51,7 +51,7 @@ public function __construct(Project $project) public function startElement($parser, $name, $attributes): void { parent::startElement($parser, $name, $attributes); - if ($name == 'SITE') { + if ($this->currentPathMatches('site')) { $site_name = !empty($attributes['NAME']) ? $attributes['NAME'] : '(empty)'; $this->Site = Site::firstOrCreate(['name' => $site_name], ['name' => $site_name]); @@ -77,9 +77,7 @@ public function startElement($parser, $name, $attributes): void /** End Element */ public function endElement($parser, $name): void { - parent::endElement($parser, $name); - - if ($name === 'SITE') { + if ($this->currentPathMatches('site')) { $start_time = gmdate(FMT_DATETIME, $this->StartTimeStamp); $end_time = gmdate(FMT_DATETIME, $this->EndTimeStamp); $this->Build->ProjectId = $this->GetProject()->Id; @@ -159,6 +157,8 @@ public function endElement($parser, $name): void $this->CoverageFiles[] = [new CoverageFile(), new CoverageFileLog()]; } } + + parent::endElement($parser, $name); } /** Text */ diff --git a/app/cdash/xml_handlers/dynamic_analysis_handler.php b/app/cdash/xml_handlers/dynamic_analysis_handler.php index 4ac862c87..bd2768857 100644 --- a/app/cdash/xml_handlers/dynamic_analysis_handler.php +++ b/app/cdash/xml_handlers/dynamic_analysis_handler.php @@ -71,7 +71,7 @@ public function startElement($parser, $name, $attributes): void parent::startElement($parser, $name, $attributes); $factory = $this->getModelFactory(); - if ($name == 'SITE') { + if ($this->currentPathMatches('site')) { $site_name = !empty($attributes['NAME']) ? $attributes['NAME'] : '(empty)'; $this->Site = Site::firstOrCreate(['name' => $site_name], ['name' => $site_name]); @@ -152,10 +152,8 @@ public function startElement($parser, $name, $attributes): void /** Function endElement */ public function endElement($parser, $name): void { - $parent = $this->getParent(); // should be before endElement - parent::endElement($parser, $name); $factory = $this->getModelFactory(); - if ($name == 'STARTTESTTIME' && $parent == 'DYNAMICANALYSIS') { + if ($name === 'STARTTESTTIME' && $this->getParent() === 'DYNAMICANALYSIS') { if (empty($this->SubProjects)) { // Not a SubProject build. $this->createBuild(''); @@ -165,7 +163,7 @@ public function endElement($parser, $name): void $this->createBuild($subproject); } } - } elseif ($name == 'TEST' && $parent == 'DYNAMICANALYSIS') { + } elseif ($name === 'TEST' && $this->getParent() == 'DYNAMICANALYSIS') { /** @var Build $build */ $build = $this->Builds[$this->SubProjectName]; $GLOBALS['PHP_ERROR_BUILD_ID'] = $build->Id; @@ -213,6 +211,8 @@ public function endElement($parser, $name): void } } } + + parent::endElement($parser, $name); } /** Function Text */ diff --git a/app/cdash/xml_handlers/note_handler.php b/app/cdash/xml_handlers/note_handler.php index 78ef5075b..2b6ef8f61 100644 --- a/app/cdash/xml_handlers/note_handler.php +++ b/app/cdash/xml_handlers/note_handler.php @@ -44,7 +44,7 @@ public function __construct(Project $project) public function startElement($parser, $name, $attributes): void { parent::startElement($parser, $name, $attributes); - if ($name == 'SITE') { + if ($this->currentPathMatches('site')) { $site_name = !empty($attributes['NAME']) ? $attributes['NAME'] : '(empty)'; $this->Site = Site::firstOrCreate(['name' => $site_name], ['name' => $site_name]); diff --git a/app/cdash/xml_handlers/testing_handler.php b/app/cdash/xml_handlers/testing_handler.php index 08a8f9ef0..7c61c8f77 100644 --- a/app/cdash/xml_handlers/testing_handler.php +++ b/app/cdash/xml_handlers/testing_handler.php @@ -75,10 +75,9 @@ public function __construct(Project $project) public function startElement($parser, $name, $attributes): void { parent::startElement($parser, $name, $attributes); - $parent = $this->getParent(); // should be before endElement $factory = $this->getModelFactory(); - if ($name == 'SITE') { + if ($this->currentPathMatches('site')) { $site_name = !empty($attributes['NAME']) ? $attributes['NAME'] : '(empty)'; $this->Site = Site::firstOrCreate(['name' => $site_name], ['name' => $site_name]); @@ -150,11 +149,11 @@ public function startElement($parser, $name, $attributes): void $this->TestMeasurement->name = $attributes['NAME']; } $this->TestMeasurement->type = $attributes['TYPE']; - } elseif ($name == 'VALUE' && $parent == 'MEASUREMENT') { + } elseif ($name === 'VALUE' && $this->getParent() === 'MEASUREMENT') { if (isset($attributes['COMPRESSION']) && $attributes['COMPRESSION'] == 'gzip') { $this->TestCreator->alreadyCompressed = true; } - } elseif ($name == 'LABEL' && $parent == 'LABELS') { + } elseif ($name === 'LABEL' && $this->getParent() === 'LABELS') { $this->Label = $factory->create(Label::class); } } @@ -162,11 +161,9 @@ public function startElement($parser, $name, $attributes): void /** End Element */ public function endElement($parser, $name): void { - $parent = $this->getParent(); // should be before endElement - parent::endElement($parser, $name); $factory = $this->getModelFactory(); - if ($name == 'TEST' && $parent == 'TESTING') { + if ($name === 'TEST' && $this->getParent() === 'TESTING') { // By now, will either have one subproject for the entire file // Or a subproject specifically for this test // Or no subprojects. @@ -190,7 +187,7 @@ public function endElement($parser, $name): void } $this->TestCreator->projectid = $this->GetProject()->Id; $this->TestCreator->create($build); - } elseif ($name == 'LABEL' && $parent == 'LABELS') { + } elseif ($name === 'LABEL' && $this->getParent() == 'LABELS') { if (!empty($this->TestSubProjectName)) { $this->SubProjectName = $this->TestSubProjectName; } @@ -226,7 +223,7 @@ public function endElement($parser, $name): void $this->TestCreator->measurements->push($this->TestMeasurement); } } - } elseif ($name == 'SITE') { + } elseif ($this->currentPathMatches('site')) { // If we've gotten this far without creating any builds, there's no // tests. Create a build anyway. if (empty($this->Builds)) { @@ -264,6 +261,8 @@ public function endElement($parser, $name): void $build->SaveTotalTestsTime(); } } + + parent::endElement($parser, $name); } /** Text function */ diff --git a/app/cdash/xml_handlers/testing_j_unit_handler.php b/app/cdash/xml_handlers/testing_j_unit_handler.php index f02b6e3f8..2340fbc70 100644 --- a/app/cdash/xml_handlers/testing_j_unit_handler.php +++ b/app/cdash/xml_handlers/testing_j_unit_handler.php @@ -70,7 +70,7 @@ public function startElement($parser, $name, $attributes): void parent::startElement($parser, $name, $attributes); $parent = $this->getParent(); // should be before endElement - if ($name == 'SITE') { + if ($this->currentPathMatches('site')) { $this->HasSiteTag = true; $site_name = !empty($attributes['NAME']) ? $attributes['NAME'] : '(empty)'; $this->Site = Site::firstOrCreate(['name' => $site_name], ['name' => $site_name]); diff --git a/app/cdash/xml_handlers/update_handler.php b/app/cdash/xml_handlers/update_handler.php index 4ef1675a4..6f6a3c888 100644 --- a/app/cdash/xml_handlers/update_handler.php +++ b/app/cdash/xml_handlers/update_handler.php @@ -73,8 +73,7 @@ public function startElement($parser, $name, $attributes): void /** End element */ public function endElement($parser, $name): void { - parent::endElement($parser, $name); - if ($name == 'SITE') { + if ($this->currentPathMatches('site')) { if (!isset($this->Site)) { $this->Site = Site::firstOrCreate(['name' => '(unknown)']); } else { @@ -142,6 +141,8 @@ public function endElement($parser, $name): void $this->Update->AddFile($this->UpdateFile); unset($this->UpdateFile); } + + parent::endElement($parser, $name); } /** Text */ diff --git a/app/cdash/xml_handlers/upload_handler.php b/app/cdash/xml_handlers/upload_handler.php index e9defca2a..ba2d03603 100644 --- a/app/cdash/xml_handlers/upload_handler.php +++ b/app/cdash/xml_handlers/upload_handler.php @@ -79,7 +79,7 @@ public function startElement($parser, $name, $attributes): void return; } - if ($name === 'SITE') { + if ($this->currentPathMatches('site')) { $site_name = !empty($attributes['NAME']) ? $attributes['NAME'] : '(empty)'; $this->Site = Site::firstOrCreate(['name' => $site_name], ['name' => $site_name]); diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a32de4d4c..6be6336e1 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -28949,11 +28949,6 @@ parameters: count: 1 path: app/cdash/xml_handlers/abstract_xml_handler.php - - - message: "#^Method AbstractXmlHandler\\:\\:getParent\\(\\) has no return type specified\\.$#" - count: 1 - path: app/cdash/xml_handlers/abstract_xml_handler.php - - message: "#^Method AbstractXmlHandler\\:\\:getSubProjectName\\(\\) has no return type specified\\.$#" count: 1 @@ -29036,7 +29031,7 @@ parameters: - message: "#^Loose comparison via \"\\=\\=\" is not allowed\\.$#" - count: 29 + count: 21 path: app/cdash/xml_handlers/build_handler.php - @@ -29246,7 +29241,7 @@ parameters: - message: "#^Loose comparison via \"\\=\\=\" is not allowed\\.$#" - count: 13 + count: 10 path: app/cdash/xml_handlers/configure_handler.php - @@ -29396,7 +29391,7 @@ parameters: - message: "#^Loose comparison via \"\\=\\=\" is not allowed\\.$#" - count: 12 + count: 10 path: app/cdash/xml_handlers/coverage_handler.php - @@ -29486,7 +29481,7 @@ parameters: - message: "#^Loose comparison via \"\\=\\=\" is not allowed\\.$#" - count: 14 + count: 12 path: app/cdash/xml_handlers/coverage_junit_handler.php - @@ -29581,7 +29576,7 @@ parameters: - message: "#^Loose comparison via \"\\=\\=\" is not allowed\\.$#" - count: 7 + count: 6 path: app/cdash/xml_handlers/coverage_log_handler.php - @@ -29796,7 +29791,7 @@ parameters: - message: "#^Loose comparison via \"\\=\\=\" is not allowed\\.$#" - count: 22 + count: 18 path: app/cdash/xml_handlers/dynamic_analysis_handler.php - @@ -29936,7 +29931,7 @@ parameters: - message: "#^Loose comparison via \"\\=\\=\" is not allowed\\.$#" - count: 5 + count: 4 path: app/cdash/xml_handlers/note_handler.php - @@ -30201,7 +30196,7 @@ parameters: - message: "#^Loose comparison via \"\\=\\=\" is not allowed\\.$#" - count: 34 + count: 25 path: app/cdash/xml_handlers/testing_handler.php - @@ -30371,7 +30366,7 @@ parameters: - message: "#^Loose comparison via \"\\=\\=\" is not allowed\\.$#" - count: 21 + count: 20 path: app/cdash/xml_handlers/testing_j_unit_handler.php - @@ -30526,7 +30521,7 @@ parameters: - message: "#^Loose comparison via \"\\=\\=\" is not allowed\\.$#" - count: 23 + count: 22 path: app/cdash/xml_handlers/update_handler.php -