Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
juzna committed Feb 15, 2014
1 parent 214570b commit 5dd21d8
Show file tree
Hide file tree
Showing 13 changed files with 535 additions and 7 deletions.
35 changes: 35 additions & 0 deletions Nette/Diagnostics/BlueScreen.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,41 @@ public function render(\Exception $exception)
}


/**
* Get enhanced stack trace for an exception
* @param \Exception
* @return array
*/
private function getStack(\Exception $ex)
{
$stack = $ex->getTrace();

foreach ($stack as &$row) {
if (isset($row['file']) && !isset($row['line']) && is_file($row['file'])) {
$this->remapStackLine($row['file'], $row['line']);
}
}
return $stack;
}



/**
* Find original source, see Helpers::sourceMapLookup()
* @param string
* @param int
* @return void
*/
private function remapStackLine(&$originalFile, &$originalLine)
{
$ret = SourceMapHelper::sourceMapLookup($originalFile, $originalLine);
if ($ret) {
list ($originalFile, $originalLine) = $ret;
}
}



/**
* Returns syntax highlighted source code.
* @param string
Expand Down
47 changes: 47 additions & 0 deletions Nette/Diagnostics/SourceMapHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace Nette\Diagnostics;


/**
* Resolves source maps
*/
class SourceMapHelper
{

/**
* Converts line number in compiled file into line number in source file (e.g. for latte templates)
*
* @param string
* @param int
* @return null|array [ sourceFile, sourceLine ] or null if not found
*/
public static function sourceMapLookup($originalFile, $originalLine)
{
$data = file_get_contents($originalFile);
if (!preg_match('~^// source file: (.+)$~m', $data, $match)) {
return;
}
$sourceFile = trim($match[1]);

if (!preg_match('~// source map: ([^\s]+)~', $data, $match)) {
return;
}
$map = json_decode($match[1]);

$compiledLine = $originalLine - substr_count($data, "\n", 0, strpos($data, $match[0])) - 1; // adjust line for source map beginning (some lines before the mapped file + 1 line of source map definition)

// find closest key
$compiledLines = array();
foreach (array_keys((array) $map) as $line) {
$compiledLines[$line] = abs($line - $compiledLine);
}
asort($compiledLines);
$closestLine = key($compiledLines);

return array($sourceFile, reset($map->$closestLine));
}

}


11 changes: 6 additions & 5 deletions Nette/Diagnostics/templates/bluescreen.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ $counter = 0;
<?php $ex = $exception; $level = 0; ?>
<?php do { ?>
<?php do { $exFile = $ex->getFile(); $exLine = $ex->getLine(); $this->remapStackLine($exFile, $exLine); ?>
<?php if ($level++): ?>
<div class="panel"<?php if ($level === 2) echo ' id="netteCaused"' ?>>
Expand All @@ -84,8 +84,8 @@ $counter = 0;
<?php endforeach ?>


<?php $stack = $ex->getTrace(); $expanded = NULL ?>
<?php if ((!$exception instanceof \ErrorException || in_array($exception->getSeverity(), array(E_USER_NOTICE, E_USER_WARNING, E_USER_DEPRECATED))) && $this->isCollapsed($ex->getFile())) {
<?php $stack = $this->getStack($ex); $expanded = NULL ?>
<?php if ((!$exception instanceof \ErrorException || in_array($exception->getSeverity(), array(E_USER_NOTICE, E_USER_WARNING, E_USER_DEPRECATED))) && $this->isCollapsed($exFile)) {
foreach ($stack as $key => $row) {
if (isset($row['file']) && !$this->isCollapsed($row['file'])) { $expanded = $key; break; }
}
Expand All @@ -95,8 +95,8 @@ $counter = 0;
<h2><a href="#netteBsPnl<?php echo ++$counter ?>" class="nette-toggle<?php echo ($collapsed = $expanded !== NULL) ? '-collapsed' : '' ?>">Source file</a></h2>

<div id="netteBsPnl<?php echo $counter ?>" class="<?php echo $collapsed ? 'nette-collapsed ' : '' ?>inner">
<p><b>File:</b> <?php echo Helpers::editorLink($ex->getFile(), $ex->getLine()) ?></p>
<?php if (is_file($ex->getFile())): ?><?php echo self::highlightFile($ex->getFile(), $ex->getLine(), 15, $ex instanceof \ErrorException && isset($ex->context) ? $ex->context : NULL) ?><?php endif ?>
<p><b>File:</b> <?php echo Helpers::editorLink($exFile, $exLine) ?></p>
<?php if (is_file($exFile)): ?><?php echo self::highlightFile($exFile, $exLine, 15, $ex instanceof \ErrorException && isset($ex->context) ? $ex->context : NULL) ?><?php endif ?>
</div></div>


Expand All @@ -108,6 +108,7 @@ $counter = 0;
<div id="netteBsPnl<?php echo $counter ?>" class="inner">
<ol>
<?php foreach ($stack as $key => $row): ?>
<?php if (isset($row['file'], $row['line'])) $this->remapStackLine($row['file'], $row['line']); ?>
<li><p>

<?php if (isset($row['file']) && is_file($row['file'])): ?>
Expand Down
46 changes: 44 additions & 2 deletions Nette/Latte/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
*/
class Compiler extends Nette\Object
{
/** @var bool should source map be created? Causes hints to be generated into code */
public static $createSourceMap = TRUE;

/** @var string default content type */
public $defaultContentType = self::CONTENT_HTML;

Expand Down Expand Up @@ -138,6 +141,17 @@ public function compile(array $tokens)
}

$output = $this->expandTokens($output);

// attach source map
if (static::$createSourceMap) {
$sourceMap = $this->extractSourceMap($output);
$hash = sha1($output);
$output = "<?php // source map $hash: " . json_encode($sourceMap) . " ?>\n"
. $output
. "\n<?php // end of source map $hash ?>";
;
}

return $output;
}

Expand Down Expand Up @@ -242,6 +256,30 @@ private function processMacroTag(Token $token)
}


/**
* Extract source map from generated code with hints
* @param string
* @return array { compiledLine => [ sourceLine ] } which maps compiled code back to its source
*/
private function extractSourceMap(&$output)
{
$map = array();
$line = 1;
$output = \Nette\Utils\Strings::replace($output, '~([^\x03-\x05]+)|\x03([^\x04]+)\x04([^\x05]*)\x05~m', function($match) use(&$map, &$line) {
if (!isset($match[2])) {
$ret = $match[1];
} else {
$map[intval($line)][] = intval($match[2]);
$ret = $match[3];
}
$line += substr_count($match[0], "\n");
return $ret;
});
return array_map('array_unique', $map);
}



private function processHtmlTagBegin(Token $token)
{
if ($token->closing) {
Expand Down Expand Up @@ -441,7 +479,11 @@ private function writeCode($code, & $output, $isRightmost, $isLeftmost = NULL)
$code .= "\n"; // double newline to avoid newline eating by PHP
}
}
$output .= $code;
if (static::$createSourceMap && !preg_match('~<\?php\s+\?>~', $code)) {
$output .= "\x03{$this->tokens[$this->position]->line}\x04$code\x05";
} else {
$output .= $code;
}
}


Expand Down Expand Up @@ -516,7 +558,7 @@ public function writeAttrsMacro($code)
}
}

if ($right && substr($this->output, -2) === '?>') {
if ($right && (self::$createSourceMap ? substr($this->output, -3, 2) : substr($this->output, -2)) === '?>') {
$this->output .= "\n";
}
}
Expand Down
26 changes: 26 additions & 0 deletions tests/Nette/Diagnostics/SourceMap.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/**
* Test: Nette\Diagnostics\Helper::sourceMapLookup()
*
* @author Jan Dolecek
* @package Nette\Diagnostics
* @subpackage UnitTests
*/

use Nette\Diagnostics\SourceMapHelper;
use Tester\Assert;



require __DIR__ . '/../bootstrap.php';


$file = __DIR__ . '/SourceMap.phtml';
Assert::same( array('SourceMap.phpt', 1), SourceMapHelper::sourceMapLookup($file, 15) );
Assert::same( array('SourceMap.phpt', 15), SourceMapHelper::sourceMapLookup($file, 16) );
Assert::same( array('SourceMap.phpt', 15), SourceMapHelper::sourceMapLookup($file, 17) );
Assert::same( NULL, SourceMapHelper::sourceMapLookup($file, 19) );

// Another source map within the same file - not supported at the moment
// Assert::same( array('SourceMap.phpt', 1), Helpers::sourceMapLookup($file, 32) );
34 changes: 34 additions & 0 deletions tests/Nette/Diagnostics/SourceMap.phtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
// Example of compiled file with source-map
$sourceMap = array(
array(4, 1), // source line 1 compiled to line 4
array(5, 15),
array(6, 15),
);
echo serialize($sourceMap);

// source file: SourceMap.phpt
//sourceMap[073e15abd403c7c814443453dce450affde014a8]000086:a:3:{i:0;a:2:{i:0;i:4;i:1;i:1;}i:1;a:2:{i:0;i:5;i:1;i:15;}i:2;a:2:{i:0;i:6;i:1;i:15;}} -- after this the payload starts
$line1 =
NULL; // of the original file

$compiledVariable1 = 1; // from original line 1
if ($x && $y) $compiledVariable15 = 2;
else $compiledVariable15 = 'ABCD';

$notFromSource = 'X';

//eof:sourceMap[073e15abd403c7c814443453dce450affde014a8]


$glueCode = 1; // not from source


// Another source map within the same file
//sourceMap[0000000000000000000000000000000000000011]000032:a:1:{i:0;a:2:{i:0;i:4;i:1;i:1;}} -- after this the payload starts
$line1 =
NULL; // of the original file

$compiledVariable1 = 1;

//eof:sourceMap[0000000000000000000000000000000000000011]
1 change: 1 addition & 0 deletions tests/Nette/Latte/Template.inc
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@ function codefix($s)
{
$s = preg_replace("#(?<=['_])[a-z0-9]{10,}(?=['_])#", 'xxx', $s);
$s = preg_replace('#source file:.*#', '', $s);
$s = preg_replace('#\s*((<\\?php )?//eof:|//)sourceMap\\[.{40}\\].*#', '', $s);
return $s;
}
98 changes: 98 additions & 0 deletions tests/Nette/Latte/expected/sourceMaps.phtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
<?php // source map %a%: {"21":[14],"25":[24],"26":[18],"27":[21],"32":[24],"35":[31],"36":[28],"37":[29],"38":[30],"40":[31],"50":[12]}


// prolog Nette\Latte\Macros\CoreMacros
list($_l, $_g) = Nette\Latte\Macros\CoreMacros::initRuntime($template, '%a%')
;
// prolog Nette\Latte\Macros\UIMacros
//
// block css
//
if (!function_exists($_l->blocks['css'][] = '%a%_css')) { function %a%_css($_l, $_args) { foreach ($_args as $__k => $__v) $$__k = $__v
?> .no-images img { display: none; }
.x .y { font-weight: bold; }
<?php
}}

//
// block content
//
if (!function_exists($_l->blocks['content'][] = '%a%_content')) { function %a%_content($_l, $_args) { foreach ($_args as $__k => $__v) $$__k = $__v
; call_user_func(reset($_l->blocks['title']), $_l, get_defined_vars()) {* original-line 12 *}

;

echo Nette\Templating\Helpers::escapeHtml($x, ENT_NOQUOTES) {* original-line 14 *}
?>


<ul>
<?php $iterations = 0; foreach ($items as $item) { ?> <li>
Item <?php echo Nette\Templating\Helpers::escapeHtml($item->id, ENT_NOQUOTES) ?>:
<a
onmouseover="console.log(this)" href="<?php echo htmlSpecialChars($_control->link("item", array($item->id))) ?>"<?php if ($_l->tmp = array_filter(array($item->superCool ? 'superCool' : NULL))) echo ' class="' . htmlSpecialChars(implode(" ", array_unique($_l->tmp))) . '"' ?>>
Link
</a>
</li>
<?php $iterations++; }
?>
</ul>

<?php Nette\Latte\Macros\FormMacros::renderFormBegin($form = $_form = $_control["myForm"], array())
?>
Name: <?php echo $_form["name"]->getControl() ?><br>
Mail: <?php echo $_form["email"]->getControl() ?><br>
<?php echo $_form["add"]->getControl()
?>

<?php Nette\Latte\Macros\FormMacros::renderFormEnd($_form)
?>

<hr>
<?php
}}

//
// block title
//
if (!function_exists($_l->blocks['title'][] = '%a%_title')) { function %a%_title($_l, $_args) { foreach ($_args as $__k => $__v) $$__k = $__v
?><h1>Page <?php echo Nette\Templating\Helpers::escapeHtml($name, ENT_NOQUOTES) ?></h1>
<?php
}}

//
// end of blocks
//

// template extending and snippets support

$_l->extends = empty($template->_extended) && isset($_control) && $_control instanceof Nette\Application\UI\Presenter ? $_control->findLayoutTemplateFile() : NULL; $template->_extended = $_extended = TRUE;


if ($_l->extends) {
ob_start();

} elseif (!empty($_control->snippetMode)) {
return Nette\Latte\Macros\UIMacros::renderSnippets($_control, $_l, get_defined_vars());
}

//
// main template
//




if ($_l->extends) { ob_end_clean(); return Nette\Latte\Macros\CoreMacros::includeTemplate($_l->extends, get_defined_vars(), $template)->render(); }
call_user_func(reset($_l->blocks['css']), $_l, get_defined_vars())


?>



<?php call_user_func(reset($_l->blocks['content']), $_l, get_defined_vars())

;

// end of source map %a%
Loading

0 comments on commit 5dd21d8

Please sign in to comment.