From 5f854d474b5bf14be94fbe5c5c634ddc83c8b5a3 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Mon, 13 May 2019 10:14:04 +0200 Subject: [PATCH 1/5] push/pop in call stack all compile children call, only @include and function call being named and displayed in error messages --- src/Compiler.php | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index b69cf43c..2ad2bed2 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -1462,11 +1462,13 @@ protected function popCallStack() * * @param array $stms * @param \Leafo\ScssPhp\Formatter\OutputBlock $out + * @param string $traceName * * @return array|null */ - protected function compileChildren($stms, OutputBlock $out) + protected function compileChildren($stms, OutputBlock $out, $traceName = '') { + $this->pushCallStack($traceName); foreach ($stms as $stm) { $ret = $this->compileChild($stm, $out); @@ -1474,6 +1476,7 @@ protected function compileChildren($stms, OutputBlock $out) return $ret; } } + $this->popCallStack(); return null; } @@ -1484,11 +1487,13 @@ protected function compileChildren($stms, OutputBlock $out) * @param array $stms * @param \Leafo\ScssPhp\Formatter\OutputBlock $out * @param \Leafo\ScssPhp\Block $selfParent + * @param string $traceName * * @throws \Exception */ - protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null) + protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent = null, $traceName = '') { + $this->pushCallStack($traceName); foreach ($stms as $stm) { if ($selfParent && isset($stm[1]) && is_object($stm[1]) && $stm[1] instanceof Block) { $stm[1]->selfParent = $selfParent; @@ -1508,6 +1513,7 @@ protected function compileChildrenNoReturn($stms, OutputBlock $out, $selfParent return; } } + $this->popCallStack(); } @@ -2160,9 +2166,7 @@ protected function compileChild($child, OutputBlock $out) $this->env->marker = 'mixin'; - $this->pushCallStack($this->env->marker . " " . $name); - $this->compileChildrenNoReturn($mixin->children, $out, $selfParent); - $this->popCallStack(); + $this->compileChildrenNoReturn($mixin->children, $out, $selfParent, $this->env->marker . " " . $name); $this->storeEnv = $storeEnv; @@ -3905,15 +3909,20 @@ public function throwError($msg) $msg = "$msg: $loc"; if ($this->callStack) { - $msg .= "\nCall Stack:\n"; + $callStackMsg = ""; $ncall = 0; foreach (array_reverse($this->callStack) as $call) { - $msg .= "#" . $ncall++ . " " . $call['n'] . " "; - $msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]]) - ? $this->sourceNames[$call[Parser::SOURCE_INDEX]] - : '(unknown file)'); - $msg .= " on line " . $call[Parser::SOURCE_LINE] . "\n"; + if (isset($call['n']) && $call['n']) { + $callStackMsg .= "#" . $ncall++ . " " . $call['n'] . " "; + $callStackMsg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]]) + ? $this->sourceNames[$call[Parser::SOURCE_INDEX]] + : '(unknown file)'); + $callStackMsg .= " on line " . $call[Parser::SOURCE_LINE] . "\n"; + } + } + if ($callStackMsg) { + $msg .= "\nCall Stack:\n" . $callStackMsg; } } @@ -3984,11 +3993,8 @@ protected function callScssFunction($name, $argValues, &$returnValue) $tmp->children = []; $this->env->marker = 'function'; - $this->pushCallStack($this->env->marker . " " . $name); - - $ret = $this->compileChildren($func->children, $tmp); - $this->popCallStack(); + $ret = $this->compileChildren($func->children, $tmp, $this->env->marker . " " . $name); $this->storeEnv = $storeEnv; From 78d9dab2748b1372be421d25c0aa539f6f357f8f Mon Sep 17 00:00:00 2001 From: Cerdic Date: Mon, 13 May 2019 10:50:54 +0200 Subject: [PATCH 2/5] Use the full callStack to detect an infinite calling loop and throw an error when its length exceeds 1000 --- src/Compiler.php | 44 +++++++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index 2ad2bed2..807040b6 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -1450,6 +1450,13 @@ protected function pushCallStack($name = '') Parser::SOURCE_LINE => $this->sourceLine, Parser::SOURCE_COLUMN => $this->sourceColumn ]; + // infinite calling loop + if (count($this->callStack) > 1000) { + // not displayed but you can var_dump it to deep debug + $msg = $this->callStackMessage(true, 100); + $msg = "Infinite calling loop"; + $this->throwError($msg); + } } protected function popCallStack() @@ -3908,25 +3915,40 @@ public function throwError($msg) : "line: $line"; $msg = "$msg: $loc"; - if ($this->callStack) { - $callStackMsg = ""; - $ncall = 0; + $callStackMsg = $this->callStackMessage(); + if ($callStackMsg) { + $msg .= "\nCall Stack:\n" . $callStackMsg; + } + + throw new CompilerException($msg); + } + /** + * @param bool $all + * @param null $limit + * @return string + */ + protected function callStackMessage($all = false, $limit = null) { + $callStackMsg = []; + $ncall = 0; + + if ($this->callStack) { foreach (array_reverse($this->callStack) as $call) { - if (isset($call['n']) && $call['n']) { - $callStackMsg .= "#" . $ncall++ . " " . $call['n'] . " "; - $callStackMsg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]]) + if ($all || (isset($call['n']) && $call['n'])) { + $msg = "#" . $ncall++ . " " . $call['n'] . " "; + $msg .= (isset($this->sourceNames[$call[Parser::SOURCE_INDEX]]) ? $this->sourceNames[$call[Parser::SOURCE_INDEX]] : '(unknown file)'); - $callStackMsg .= " on line " . $call[Parser::SOURCE_LINE] . "\n"; + $msg .= " on line " . $call[Parser::SOURCE_LINE]; + $callStackMsg[] = $msg; + if (!is_null($limit) && $ncall>$limit) { + break; + } } } - if ($callStackMsg) { - $msg .= "\nCall Stack:\n" . $callStackMsg; - } } - throw new CompilerException($msg); + return implode("\n", $callStackMsg); } /** From 8d967ab13ee4c6ac5c534818bfc6a38e62f68d76 Mon Sep 17 00:00:00 2001 From: Cerdic Date: Mon, 13 May 2019 11:16:16 +0200 Subject: [PATCH 3/5] Increase callstack length thresold to be able to compile big framework like Foundation (erm...) --- src/Compiler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler.php b/src/Compiler.php index 807040b6..f5bdaccc 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -1451,7 +1451,7 @@ protected function pushCallStack($name = '') Parser::SOURCE_COLUMN => $this->sourceColumn ]; // infinite calling loop - if (count($this->callStack) > 1000) { + if (count($this->callStack) > 25000) { // not displayed but you can var_dump it to deep debug $msg = $this->callStackMessage(true, 100); $msg = "Infinite calling loop"; From 1e6ca86956d8e5a80255984644757aeeba35f65a Mon Sep 17 00:00:00 2001 From: Cerdic Date: Mon, 13 May 2019 11:17:53 +0200 Subject: [PATCH 4/5] Fix https://github.com/leafo/scssphp/issues/432: If there is a storeEnv you should take the @content from it, even if it's emtpy, not pickink it from the env (which would leads to a mistaken @content) --- src/Compiler.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Compiler.php b/src/Compiler.php index f5bdaccc..c5a7401c 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -2181,8 +2181,7 @@ protected function compileChild($child, OutputBlock $out) break; case Type::T_MIXIN_CONTENT: - $content = $this->get(static::$namespaces['special'] . 'content', false, $this->getStoreEnv()) - ?: $this->get(static::$namespaces['special'] . 'content', false, $this->env); + $content = $this->get(static::$namespaces['special'] . 'content', false, isset($this->storeEnv) ? $this->storeEnv : $this->env); if (! $content) { $content = new \stdClass(); From 1a7a8ec561564eb722b1dd8ed7679bcce8677a1c Mon Sep 17 00:00:00 2001 From: Cerdic Date: Mon, 13 May 2019 11:24:45 +0200 Subject: [PATCH 5/5] PSR2 --- src/Compiler.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Compiler.php b/src/Compiler.php index c5a7401c..62ef74d1 100644 --- a/src/Compiler.php +++ b/src/Compiler.php @@ -3927,7 +3927,8 @@ public function throwError($msg) * @param null $limit * @return string */ - protected function callStackMessage($all = false, $limit = null) { + protected function callStackMessage($all = false, $limit = null) + { $callStackMsg = []; $ncall = 0;