Skip to content

Commit

Permalink
Runtime: Isolates scope when resolving values to prevent overriding p…
Browse files Browse the repository at this point in the history
…age data (#5668)

Co-authored-by: Jason Varga <jason@pixelfear.com>
  • Loading branch information
JohnathonKoster and jasonvarga committed Apr 11, 2022
1 parent 5154c95 commit f4cdf20
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 16 deletions.
4 changes: 3 additions & 1 deletion src/Providers/ViewServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ private function registerRuntimeAntlers()

$isTracingOn = config('statamic.antlers.tracing', false);
$runtimeConfig->fatalErrorOnUnpairedLoop = config('statamic.antlers.fatalErrorOnUnpairedLoop', false);
$runtimeConfig->fatalErrorOnStringObject = config('statamic.antlers.fatalErrorOnPrintObjects', false);
$runtimeConfig->throwErrorOnAccessViolation = config('statamic.antlers.errorOnAccessViolation', false);
$runtimeConfig->guardedVariablePatterns = config('statamic.antlers.guardedVariables', [
'config.app.key',
Expand Down Expand Up @@ -145,7 +146,8 @@ private function registerRuntimeAntlers()
$runtimeConfig->traceManager->registerTracer(GlobalDebugManager::getTimingsTracer());
}

$parser->setRuntimeConfiguration($runtimeConfig);
$parser->isolateRuntimes(GlobalRuntimeState::$requiresRuntimeIsolation)
->setRuntimeConfiguration($runtimeConfig);

return $parser;
});
Expand Down
1 change: 1 addition & 0 deletions src/View/Antlers/Language/Errors/AntlersErrorCodes.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,5 @@ class AntlersErrorCodes
const TYPE_MODIFIER_INCORRECT_VALUE_POSITION = 'ANTLR_128';

const TYPE_MODIFIER_UNEXPECTED_TOKEN_METHOD_SYNTAX = 'ANTLR_129';
const TYPE_RUNTIME_ATTEMPTING_TO_RENDER_OBJECT_AS_STRING = 'ANTLR_130';
}
19 changes: 10 additions & 9 deletions src/View/Antlers/Language/Runtime/Debugging/ScopeDumper.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Statamic\Stache\Query\EntryQueryBuilder;
use Statamic\Structures\Page;
use Statamic\Taxonomies\Taxonomy;
use Statamic\View\Antlers\Language\Runtime\Sandbox\RuntimeValueCache;

class ScopeDumper
{
Expand Down Expand Up @@ -172,7 +173,7 @@ private function createVariables($data)
} elseif ($v instanceof Site) {
self::$varIndexCount += 1;
$thisVar = self::$varIndexCount;
$this->additionalVars[$thisVar] = $this->createVariables($v->toAugmentedArray());
$this->additionalVars[$thisVar] = $this->createVariables(RuntimeValueCache::resolveWithRuntimeIsolation($v));

$variable->value = '{site}';
$variable->variablesReference = $thisVar;
Expand Down Expand Up @@ -200,7 +201,7 @@ private function createVariables($data)
$resolvedValue = null;

if ($v instanceof Augmentable) {
$resolvedValue = $v->toAugmentedArray();
$resolvedValue = RuntimeValueCache::resolveWithRuntimeIsolation($v);
} else {
$resolvedValue = $v->value();
}
Expand Down Expand Up @@ -240,7 +241,7 @@ private function createVariables($data)
} elseif ($v instanceof Collection) {
self::$varIndexCount += 1;
$thisVar = self::$varIndexCount;
$subVar = $this->createVariables($v->toAugmentedArray());
$subVar = $this->createVariables(RuntimeValueCache::resolveWithRuntimeIsolation($v));
$this->additionalVars[$thisVar] = $subVar;

$variable->value = '{Entries\Collection['.count($subVar).']}';
Expand All @@ -250,7 +251,7 @@ private function createVariables($data)
} elseif ($v instanceof Page) {
self::$varIndexCount += 1;
$thisVar = self::$varIndexCount;
$subVar = $this->createVariables($v->toAugmentedArray());
$subVar = $this->createVariables(RuntimeValueCache::resolveWithRuntimeIsolation($v));
$this->additionalVars[$thisVar] = $subVar;

$variable->value = '{Page['.count($subVar).']}';
Expand All @@ -272,7 +273,7 @@ private function createVariables($data)
} elseif ($v instanceof Entry) {
self::$varIndexCount += 1;
$thisVar = self::$varIndexCount;
$subVar = $this->createVariables($v->toAugmentedArray());
$subVar = $this->createVariables(RuntimeValueCache::resolveWithRuntimeIsolation($v));
$this->additionalVars[$thisVar] = $subVar;

$variable->value = '{Entry['.count($subVar).']}';
Expand All @@ -282,7 +283,7 @@ private function createVariables($data)
} elseif ($v instanceof Blueprint) {
self::$varIndexCount += 1;
$thisVar = self::$varIndexCount;
$subVar = $this->createVariables($v->toAugmentedArray());
$subVar = $this->createVariables(RuntimeValueCache::resolveWithRuntimeIsolation($v));
$this->additionalVars[$thisVar] = $subVar;

$variable->value = '{Blueprint['.count($subVar).']}';
Expand All @@ -292,7 +293,7 @@ private function createVariables($data)
} elseif ($v instanceof User) {
self::$varIndexCount += 1;
$thisVar = self::$varIndexCount;
$subVar = $this->createVariables($v->toAugmentedArray());
$subVar = $this->createVariables(RuntimeValueCache::resolveWithRuntimeIsolation($v));
$this->additionalVars[$thisVar] = $subVar;

$variable->value = '{User['.count($subVar).']}';
Expand All @@ -302,7 +303,7 @@ private function createVariables($data)
} elseif ($v instanceof \Statamic\Auth\Eloquent\User) {
self::$varIndexCount += 1;
$thisVar = self::$varIndexCount;
$subVar = $this->createVariables($v->toAugmentedArray());
$subVar = $this->createVariables(RuntimeValueCache::resolveWithRuntimeIsolation($v));
$this->additionalVars[$thisVar] = $subVar;

$variable->value = '{EloquentUser['.count($subVar).']}';
Expand All @@ -312,7 +313,7 @@ private function createVariables($data)
} elseif ($v instanceof Taxonomy) {
self::$varIndexCount += 1;
$thisVar = self::$varIndexCount;
$subVar = $this->createVariables($v->toAugmentedArray());
$subVar = $this->createVariables(RuntimeValueCache::resolveWithRuntimeIsolation($v));
$this->additionalVars[$thisVar] = $subVar;

$variable->value = '{Taxonomy['.count($subVar).']}';
Expand Down
3 changes: 3 additions & 0 deletions src/View/Antlers/Language/Runtime/GlobalRuntimeState.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class GlobalRuntimeState
* @var bool
*/
public static $isEvaluatingUserData = false;
public static $isEvaluatingData = false;

/**
* A counter of the active tracer count.
Expand Down Expand Up @@ -184,6 +185,8 @@ public static function mergeTagRuntimeAssignments($assignments)

public static $containsLayout = false;

public static $requiresRuntimeIsolation = false;

public static $userContentEvalState = null;

/**
Expand Down
32 changes: 29 additions & 3 deletions src/View/Antlers/Language/Runtime/NodeProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,8 @@ public function render($nodes)
* @param AntlersNode $node The reference node.
* @param mixed $value The runtime value.
* @return bool
*
* @throws RuntimeException
*/
private function guardRuntime(AntlersNode $node, $value)
{
Expand All @@ -720,6 +722,15 @@ private function guardRuntime(AntlersNode $node, $value)
}

$varName = $node->name->getContent();

if ($this->runtimeConfiguration != null && $this->runtimeConfiguration->fatalErrorOnStringObject) {
throw ErrorFactory::makeRuntimeError(
AntlersErrorCodes::TYPE_RUNTIME_ATTEMPTING_TO_RENDER_OBJECT_AS_STRING,
$node,
'Fatal Error: Attempting to render object as string.'
);
}

Log::debug("Cannot render an object variable as a string: {{ {$varName} }}");

return false;
Expand Down Expand Up @@ -776,7 +787,9 @@ public function cloneProcessor()
{
$processor = new NodeProcessor($this->loader, $this->envDetails);
$processor->allowPhp($this->allowPhp);
$processor->setAntlersParserInstance($this->antlersParser);
if ($this->antlersParser != null) {
$processor->setAntlersParserInstance($this->antlersParser);
}
$processor->cascade($this->cascade);

if ($this->runtimeConfiguration != null) {
Expand Down Expand Up @@ -1258,7 +1271,11 @@ public function reduce($processNodes)
$currentProcessorCanHandleTagValue = false;
$tagCallbackResult = null;

if ($this->shouldProcessAsTag($node)) {
$lockData = $this->data;
$shouldProcessAsTag = $this->shouldProcessAsTag($node);
$this->data = $lockData;

if ($shouldProcessAsTag) {
$tagName = $node->name->getCompoundTagName();
$tagMethod = $node->name->getMethodName();

Expand Down Expand Up @@ -1796,7 +1813,7 @@ public function reduce($processNodes)
$this->pathDataManager->setIsPaired($curIsPaired);
$this->pathDataManager->setReduceFinal($curReduceFinal);

if (! $this->shouldProcessAsTag($node) && $val !== null) {
if (! $shouldProcessAsTag && $val !== null) {
foreach ($node->parameters as $param) {
if (ModifierManager::isModifier($param)) {
$activeData = $this->getActiveData();
Expand Down Expand Up @@ -1846,15 +1863,20 @@ public function reduce($processNodes)
if ($val instanceof Value) {
if ($val->shouldParseAntlers()) {
GlobalRuntimeState::$isEvaluatingUserData = true;
GlobalRuntimeState::$isEvaluatingData = true;
GlobalRuntimeState::$userContentEvalState = [
$val,
$node,
];

$val = $val->antlersValue($this->antlersParser, $this->getActiveData());
GlobalRuntimeState::$userContentEvalState = null;
GlobalRuntimeState::$isEvaluatingUserData = false;
GlobalRuntimeState::$isEvaluatingData = false;
} else {
GlobalRuntimeState::$isEvaluatingData = true;
$val = $val->value();
GlobalRuntimeState::$isEvaluatingData = false;
}
}

Expand All @@ -1874,11 +1896,15 @@ public function reduce($processNodes)
} else {
if ($param->name === 'raw') {
if ($val instanceof Value) {
GlobalRuntimeState::$isEvaluatingData = true;
$val = $val->raw();
GlobalRuntimeState::$isEvaluatingData = false;
}
} elseif ($param->name === 'noparse') {
if ($val instanceof Value) {
GlobalRuntimeState::$isEvaluatingData = true;
$val = $val->value();
GlobalRuntimeState::$isEvaluatingData = false;
}
} else {
// Throw an exception here to maintain consistent behavior with the regex parser.
Expand Down
18 changes: 17 additions & 1 deletion src/View/Antlers/Language/Runtime/PathDataManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -777,9 +777,11 @@ public static function reduce($value, $isPair = true, $reduceBuildersAndAugmenta

if ($reductionValue instanceof Value) {
GlobalRuntimeState::$isEvaluatingUserData = true;
GlobalRuntimeState::$isEvaluatingData = true;
$augmented = RuntimeValueCache::getValue($reductionValue);
$augmented = self::guardRuntimeReturnValue($augmented);
GlobalRuntimeState::$isEvaluatingUserData = false;
GlobalRuntimeState::$isEvaluatingData = false;

if (! $isPair) {
return $augmented;
Expand All @@ -788,32 +790,44 @@ public static function reduce($value, $isPair = true, $reduceBuildersAndAugmenta
$reductionStack[] = $augmented;
continue;
} elseif ($reductionValue instanceof \Statamic\Entries\Collection) {
$reductionStack[] = $reductionValue->toAugmentedArray();
GlobalRuntimeState::$isEvaluatingData = true;
$reductionStack[] = RuntimeValueCache::resolveWithRuntimeIsolation($reductionValue);
GlobalRuntimeState::$isEvaluatingData = false;
continue;
} elseif ($reductionValue instanceof ArrayableString) {
GlobalRuntimeState::$isEvaluatingData = true;
$reductionStack[] = $reductionValue->toArray();
GlobalRuntimeState::$isEvaluatingData = false;
continue;
} elseif ($reductionValue instanceof Augmentable) {
// Avoids resolving augmented data "too early".
if ($reduceBuildersAndAugmentables) {
GlobalRuntimeState::$isEvaluatingUserData = true;
GlobalRuntimeState::$isEvaluatingData = true;
$augmented = RuntimeValueCache::getAugmentableValue($reductionValue);
$augmented = self::guardRuntimeReturnValue($augmented);
GlobalRuntimeState::$isEvaluatingUserData = false;
GlobalRuntimeState::$isEvaluatingData = false;
$reductionStack[] = $augmented;
} else {
return $reductionValue;
}

continue;
} elseif ($reductionValue instanceof Collection) {
GlobalRuntimeState::$isEvaluatingData = true;
$reductionStack[] = $reductionValue->all();
GlobalRuntimeState::$isEvaluatingData = false;
continue;
} elseif ($reductionValue instanceof Arrayable) {
GlobalRuntimeState::$isEvaluatingData = true;
$reductionStack[] = $reductionValue->toArray();
GlobalRuntimeState::$isEvaluatingData = false;
continue;
} elseif ($reductionValue instanceof Builder && $reduceBuildersAndAugmentables) {
GlobalRuntimeState::$isEvaluatingData = true;
$reductionStack[] = $reductionValue->get();
GlobalRuntimeState::$isEvaluatingData = false;
continue;
}

Expand All @@ -835,6 +849,7 @@ public static function reduce($value, $isPair = true, $reduceBuildersAndAugmenta
public static function reduceForAntlers($value, Parser $parser, $data, $isPair = true)
{
GlobalRuntimeState::$isEvaluatingUserData = true;
GlobalRuntimeState::$isEvaluatingData = true;

if ($value instanceof Collection) {
$value = $value->all();
Expand Down Expand Up @@ -866,6 +881,7 @@ public static function reduceForAntlers($value, Parser $parser, $data, $isPair =
}

GlobalRuntimeState::$isEvaluatingUserData = false;
GlobalRuntimeState::$isEvaluatingData = false;

return $returnValue;
}
Expand Down
7 changes: 7 additions & 0 deletions src/View/Antlers/Language/Runtime/RuntimeConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ class RuntimeConfiguration
*/
public $fatalErrorOnUnpairedLoop = false;

/**
* Controls whether attempting to render an object as a string throws an exception or not.
*
* @var bool
*/
public $fatalErrorOnStringObject = false;

/**
* Controls whether runtime tracing is enabled.
*
Expand Down
27 changes: 27 additions & 0 deletions src/View/Antlers/Language/Runtime/RuntimeParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ class RuntimeParser implements Parser
*/
private $parseStack = 0;

/**
* Determines if runtime processors should be isolated.
*
* @var bool
*/
private $isolateRuntimes = false;

public function __construct(DocumentParser $documentParser, NodeProcessor $nodeProcessor, AntlersLexer $lexer, LanguageParser $antlersParser)
{
$this->documentParser = $documentParser;
Expand Down Expand Up @@ -655,8 +662,28 @@ private function buildStackTrace(AbstractNode $activeNode, $documentText)
return $stackTrace;
}

public function isolateRuntimes($isolate)
{
$this->isolateRuntimes = $isolate;

return $this;
}

private function cloneRuntimeParser()
{
return (new RuntimeParser(
$this->documentParser,
$this->nodeProcessor->cloneProcessor(),
$this->antlersLexer, $this->antlersParser
))->allowPhp($this->allowPhp);
}

public function parse($text, $data = [])
{
if ($this->isolateRuntimes || GlobalRuntimeState::$isEvaluatingData) {
return $this->cloneRuntimeParser()->renderText($text, $data);
}

return $this->renderText($text, $data);
}

Expand Down
14 changes: 12 additions & 2 deletions src/View/Antlers/Language/Runtime/Sandbox/RuntimeValueCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Statamic\Fields\Value;
use Statamic\Globals\Variables;
use Statamic\Sites\Site;
use Statamic\View\Antlers\Language\Runtime\GlobalRuntimeState;

class RuntimeValueCache
{
Expand Down Expand Up @@ -51,19 +52,28 @@ protected static function getRawKey($value)
return serialize($value);
}

public static function resolveWithRuntimeIsolation($augmentable)
{
GlobalRuntimeState::$requiresRuntimeIsolation = true;
$value = $augmentable->toAugmentedArray();
GlobalRuntimeState::$requiresRuntimeIsolation = false;

return $value;
}

public static function getAugmentableValue(Augmentable $augmentable)
{
$augmentKey = self::getAugmentableKey($augmentable);

if ($augmentKey != null) {
if (! array_key_exists($augmentKey, self::$runtimeValueCache)) {
self::$runtimeValueCache[$augmentKey] = $augmentable->toAugmentedArray();
self::$runtimeValueCache[$augmentKey] = self::resolveWithRuntimeIsolation($augmentable);
}

return self::$runtimeValueCache[$augmentKey];
}

return $augmentable->toAugmentedArray();
return self::resolveWithRuntimeIsolation($augmentable);
}

public static function getValue(Value $value)
Expand Down

0 comments on commit f4cdf20

Please sign in to comment.