Current File : /var/www/prestashop/modules/ps_mbo/vendor/sentry/sentry/src/FrameBuilder.php |
<?php
declare(strict_types=1);
namespace Sentry;
use Sentry\Serializer\RepresentationSerializerInterface;
/**
* This class builds a {@see Frame} object out of a backtrace's raw frame.
*
* @internal
*
* @psalm-type StacktraceFrame array{
* function?: string,
* line?: int,
* file?: string,
* class?: class-string,
* type?: string,
* args?: mixed[]
* }
*/
final class FrameBuilder
{
/**
* @var Options The SDK client options
*/
private $options;
/**
* @var RepresentationSerializerInterface The representation serializer
*/
private $representationSerializer;
/**
* Constructor.
*
* @param Options $options The SDK client options
* @param RepresentationSerializerInterface $representationSerializer The representation serializer
*/
public function __construct(Options $options, RepresentationSerializerInterface $representationSerializer)
{
$this->options = $options;
$this->representationSerializer = $representationSerializer;
}
/**
* Builds a {@see Frame} object from the given backtrace's raw frame.
*
* @param string $file The file where the frame originated
* @param int $line The line at which the frame originated
* @param array<string, mixed> $backtraceFrame The raw frame
*
* @psalm-param StacktraceFrame $backtraceFrame
*/
public function buildFromBacktraceFrame(string $file, int $line, array $backtraceFrame): Frame
{
// The filename can be in any of these formats:
// - </path/to/filename>
// - </path/to/filename>(<line number>) : eval()'d code
// - </path/to/filename>(<line number>) : runtime-created function
if (preg_match('/^(.*)\((\d+)\) : (?:eval\(\)\'d code|runtime-created function)$/', $file, $matches)) {
$file = $matches[1];
$line = (int) $matches[2];
}
$functionName = null;
$rawFunctionName = null;
$strippedFilePath = $this->stripPrefixFromFilePath($file);
if (isset($backtraceFrame['class']) && isset($backtraceFrame['function'])) {
$functionName = $backtraceFrame['class'];
if (str_starts_with($functionName, Frame::ANONYMOUS_CLASS_PREFIX)) {
$functionName = Frame::ANONYMOUS_CLASS_PREFIX . $this->stripPrefixFromFilePath(substr($backtraceFrame['class'], \strlen(Frame::ANONYMOUS_CLASS_PREFIX)));
}
$rawFunctionName = sprintf('%s::%s', $backtraceFrame['class'], $backtraceFrame['function']);
$functionName = sprintf('%s::%s', preg_replace('/(?::\d+\$|0x)[a-fA-F0-9]+$/', '', $functionName), $backtraceFrame['function']);
} elseif (isset($backtraceFrame['function'])) {
$functionName = $backtraceFrame['function'];
}
return new Frame(
$functionName,
$strippedFilePath,
$line,
$rawFunctionName,
Frame::INTERNAL_FRAME_FILENAME !== $file ? $file : null,
$this->getFunctionArguments($backtraceFrame),
$this->isFrameInApp($file, $functionName)
);
}
/**
* Removes from the given file path the specified prefixes.
*
* @param string $filePath The path to the file
*/
private function stripPrefixFromFilePath(string $filePath): string
{
foreach ($this->options->getPrefixes() as $prefix) {
if (str_starts_with($filePath, $prefix)) {
return mb_substr($filePath, mb_strlen($prefix));
}
}
return $filePath;
}
/**
* Checks whether a certain frame should be marked as "in app" or not.
*
* @param string $file The file to check
* @param string|null $functionName The name of the function
*/
private function isFrameInApp(string $file, ?string $functionName): bool
{
if (Frame::INTERNAL_FRAME_FILENAME === $file) {
return false;
}
if (null !== $functionName && str_starts_with($functionName, 'Sentry\\')) {
return false;
}
$excludedAppPaths = $this->options->getInAppExcludedPaths();
$includedAppPaths = $this->options->getInAppIncludedPaths();
$absoluteFilePath = @realpath($file) ?: $file;
$isInApp = true;
foreach ($excludedAppPaths as $excludedAppPath) {
if (str_starts_with($absoluteFilePath, $excludedAppPath)) {
$isInApp = false;
break;
}
}
foreach ($includedAppPaths as $includedAppPath) {
if (str_starts_with($absoluteFilePath, $includedAppPath)) {
$isInApp = true;
break;
}
}
return $isInApp;
}
/**
* Gets the arguments of the function called in the given frame.
*
* @param array<string, mixed> $backtraceFrame The frame data
*
* @return array<string, mixed>
*
* @psalm-param StacktraceFrame $backtraceFrame
*/
private function getFunctionArguments(array $backtraceFrame): array
{
if (!isset($backtraceFrame['function'], $backtraceFrame['args'])) {
return [];
}
$reflectionFunction = null;
try {
if (isset($backtraceFrame['class'])) {
if (method_exists($backtraceFrame['class'], $backtraceFrame['function'])) {
$reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], $backtraceFrame['function']);
} elseif (isset($backtraceFrame['type']) && '::' === $backtraceFrame['type']) {
$reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__callStatic');
} else {
$reflectionFunction = new \ReflectionMethod($backtraceFrame['class'], '__call');
}
} elseif (!\in_array($backtraceFrame['function'], ['{closure}', '__lambda_func'], true) && \function_exists($backtraceFrame['function'])) {
$reflectionFunction = new \ReflectionFunction($backtraceFrame['function']);
}
} catch (\ReflectionException $e) {
// Reflection failed, we do nothing instead
}
$argumentValues = [];
if (null !== $reflectionFunction) {
$argumentValues = $this->getFunctionArgumentValues($reflectionFunction, $backtraceFrame['args']);
} else {
foreach ($backtraceFrame['args'] as $parameterPosition => $parameterValue) {
$argumentValues['param' . $parameterPosition] = $parameterValue;
}
}
foreach ($argumentValues as $argumentName => $argumentValue) {
$argumentValues[$argumentName] = $this->representationSerializer->representationSerialize($argumentValue);
}
return $argumentValues;
}
/**
* Gets an hashmap indexed by argument name containing all the arguments
* passed to the function called in the given frame of the stacktrace.
*
* @param \ReflectionFunctionAbstract $reflectionFunction A reflection object
* @param mixed[] $backtraceFrameArgs The arguments of the frame
*
* @return array<string, mixed>
*/
private function getFunctionArgumentValues(\ReflectionFunctionAbstract $reflectionFunction, array $backtraceFrameArgs): array
{
$argumentValues = [];
foreach ($reflectionFunction->getParameters() as $reflectionParameter) {
$parameterPosition = $reflectionParameter->getPosition();
if (!isset($backtraceFrameArgs[$parameterPosition])) {
continue;
}
$argumentValues[$reflectionParameter->getName()] = $backtraceFrameArgs[$parameterPosition];
}
return $argumentValues;
}
}