@@ -35,6 +35,7 @@ class Parser
3535 private $ refs = array ();
3636 private $ skippedLineNumbers = array ();
3737 private $ locallySkippedLineNumbers = array ();
38+ private $ refsBeingParsed = array ();
3839
3940 /**
4041 * Parses a YAML file into a PHP value.
@@ -169,6 +170,7 @@ private function doParse(string $value, int $flags)
169170
170171 if (isset ($ values ['value ' ]) && '& ' === $ values ['value ' ][0 ] && self ::preg_match ('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u ' , $ values ['value ' ], $ matches )) {
171172 $ isRef = $ matches ['ref ' ];
173+ $ this ->refsBeingParsed [] = $ isRef ;
172174 $ values ['value ' ] = $ matches ['value ' ];
173175 }
174176
@@ -201,6 +203,7 @@ private function doParse(string $value, int $flags)
201203 }
202204 if ($ isRef ) {
203205 $ this ->refs [$ isRef ] = end ($ data );
206+ array_pop ($ this ->refsBeingParsed );
204207 }
205208 } elseif (
206209 self ::preg_match ('#^(?P<key>(?:![^\s]++\s++)?(?: ' .Inline::REGEX_QUOTED_STRING .'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:(\s++(?P<value>.+))?$#u ' , rtrim ($ this ->currentLine ), $ values )
@@ -235,6 +238,10 @@ private function doParse(string $value, int $flags)
235238 if (isset ($ values ['value ' ][0 ]) && '* ' === $ values ['value ' ][0 ]) {
236239 $ refName = substr (rtrim ($ values ['value ' ]), 1 );
237240 if (!array_key_exists ($ refName , $ this ->refs )) {
241+ if (false !== $ pos = array_search ($ refName , $ this ->refsBeingParsed , true )) {
242+ throw new ParseException (sprintf ('Circular reference [%s, %s] detected for reference "%s". ' , implode (', ' , \array_slice ($ this ->refsBeingParsed , $ pos )), $ refName , $ refName ), $ this ->currentLineNb + 1 , $ this ->currentLine , $ this ->filename );
243+ }
244+
238245 throw new ParseException (sprintf ('Reference "%s" does not exist. ' , $ refName ), $ this ->getRealCurrentLineNb () + 1 , $ this ->currentLine , $ this ->filename );
239246 }
240247
@@ -288,6 +295,7 @@ private function doParse(string $value, int $flags)
288295 }
289296 } elseif ('<< ' !== $ key && isset ($ values ['value ' ]) && '& ' === $ values ['value ' ][0 ] && self ::preg_match ('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u ' , $ values ['value ' ], $ matches )) {
290297 $ isRef = $ matches ['ref ' ];
298+ $ this ->refsBeingParsed [] = $ isRef ;
291299 $ values ['value ' ] = $ matches ['value ' ];
292300 }
293301
@@ -345,6 +353,7 @@ private function doParse(string $value, int $flags)
345353 }
346354 if ($ isRef ) {
347355 $ this ->refs [$ isRef ] = $ data [$ key ];
356+ array_pop ($ this ->refsBeingParsed );
348357 }
349358 } else {
350359 // multiple documents are not supported
@@ -450,6 +459,7 @@ private function parseBlock(int $offset, string $yaml, int $flags)
450459 $ parser ->totalNumberOfLines = $ this ->totalNumberOfLines ;
451460 $ parser ->skippedLineNumbers = $ skippedLineNumbers ;
452461 $ parser ->refs = &$ this ->refs ;
462+ $ parser ->refsBeingParsed = $ this ->refsBeingParsed ;
453463
454464 return $ parser ->doParse ($ yaml , $ flags );
455465 }
@@ -639,6 +649,10 @@ private function parseValue(string $value, int $flags, string $context)
639649 }
640650
641651 if (!array_key_exists ($ value , $ this ->refs )) {
652+ if (false !== $ pos = array_search ($ value , $ this ->refsBeingParsed , true )) {
653+ throw new ParseException (sprintf ('Circular reference [%s, %s] detected for reference "%s". ' , implode (', ' , \array_slice ($ this ->refsBeingParsed , $ pos )), $ value , $ value ), $ this ->currentLineNb + 1 , $ this ->currentLine , $ this ->filename );
654+ }
655+
642656 throw new ParseException (sprintf ('Reference "%s" does not exist. ' , $ value ), $ this ->currentLineNb + 1 , $ this ->currentLine , $ this ->filename );
643657 }
644658
0 commit comments