Skip to content

Commit

Permalink
Debugger: De-dup arrays and string in VariableTable (#946)
Browse files Browse the repository at this point in the history
* If a variable's id (hash value) is defined, use it to dedup entries in the VariableTable

* Fix phpcs
  • Loading branch information
chingor13 authored and dwsupplee committed Apr 3, 2018
1 parent 5aa1e67 commit 1f10fab
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 53 deletions.
7 changes: 4 additions & 3 deletions Debugger/src/Breakpoint.php
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,8 @@ public function addStackFrame($stackFrameData)

foreach ($stackFrameData['locals'] as $local) {
$value = isset($local['value']) ? $local['value'] : null;
$variable = $this->addVariable($local['name'], $value);
$hash = isset($local['id']) ? $local['id'] : null;
$variable = $this->addVariable($local['name'], $value, $hash);
$sf->addLocal($variable);
}

Expand Down Expand Up @@ -615,10 +616,10 @@ private function setError($type, $message, array $parameters = [])
);
}

private function addVariable($name, $value)
private function addVariable($name, $value, $hash = null)
{
$this->variableTable = $this->variableTable ?: new VariableTable();
return $this->variableTable->register($name, $value);
return $this->variableTable->register($name, $value, $hash);
}

private function ensureExtensionLoaded()
Expand Down
109 changes: 62 additions & 47 deletions Debugger/src/VariableTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,12 @@ public function __construct(array $initialVariables = [])
*
* @param string $name The name of the variable
* @param mixed $value The value of the variable
* @param string|null $hash [optional] The object hash to use for deduping
* @return Variable
*/
public function register($name, $value)
public function register($name, $value, $hash = null)
{
return $this->doRegister($name, $value, 0);
return $this->doRegister($name, $value, 0, $hash);
}

/**
Expand Down Expand Up @@ -89,55 +90,29 @@ public function variables()
return $this->variables;
}

private function doRegister($name, $value, $depth, $index = null)
private function doRegister($name, $value, $depth, $hash)
{
$name = (string)$name;
$type = $this->calcuateType($value);
$members = [];
$shared = false;
$type = gettype($value);
$variableValue = null;

switch ($type) {
// If the variable already exists in the table (via object hash), then
// return a reference Variable to that VariableTable entry.
$hash = $hash ?: $this->calculateHash($value);
if ($hash && array_key_exists($hash, $this->sharedVariableIndex)) {
return new Variable($name, $type, [
'varTableIndex' => $this->sharedVariableIndex[$hash]
]);
}

switch (gettype($value)) {
case 'object':
$type = get_class($value);
$hash = spl_object_hash($value);

if (array_key_exists($hash, $this->sharedVariableIndex)) {
$index = $this->sharedVariableIndex[$hash];
$shared = true;
} else {
$index = $this->nextIndex;
$this->sharedVariableIndex[$hash] = $index;

$members = [];
if ($depth < self::MAX_MEMBER_DEPTH) {
foreach (get_object_vars($value) as $key => $member) {
array_push($members, $this->doRegister($key, $member, $depth + 1));
}
}

$this->nextIndex++;
array_push($this->variables, new Variable($name, $type, [
'value' => "$type ($hash)",
'members' => $members
]));
}
return new Variable($name, $type, [
'varTableIndex' => $index
]);
$variableValue = "$type ($hash)";
$members = $this->doRegisterMembers(get_object_vars($value), $depth);
break;
case 'array':
$arraySize = count($value);
$members = [];
if ($depth < self::MAX_MEMBER_DEPTH) {
foreach ($value as $key => $member) {
array_push($members, $this->doRegister($key, $member, $depth + 1));
}
}
return new Variable($name, $type, [
'value' => "array ($arraySize)",
'members' => $members
]);
$variableValue = "array ($arraySize)";
$members = $this->doRegisterMembers($value, $depth);
break;
case 'NULL':
$variableValue = 'NULL';
Expand All @@ -146,8 +121,48 @@ private function doRegister($name, $value, $depth, $index = null)
$variableValue = (string)$value;
}

return new Variable($name, $type, [
'value' => $variableValue
]);
if ($hash) {
// If this variable has an object hash, then save it in the
// VariableTable and return a reference Variable to that entry.
$index = $this->nextIndex;
$this->sharedVariableIndex[$hash] = $index;
$this->nextIndex++;

array_push($this->variables, new Variable($name, $type, [
'value' => $variableValue,
'members' => $members
]));
return new Variable($name, $type, [
'varTableIndex' => $index
]);
} else {
return new Variable($name, $type, [
'value' => $variableValue,
'members' => $members
]);
}
}

private function calcuateType($value)
{
return is_object($value)
? get_class($value)
: gettype($value);
}

private function calculateHash($value)
{
return is_object($value) ? spl_object_hash($value) : null;
}

private function doRegisterMembers($array, $depth)
{
$members = [];
if ($depth < self::MAX_MEMBER_DEPTH) {
foreach ($array as $key => $member) {
$members[] = $this->doRegister($key, $member, $depth + 1, null);
}
}
return $members;
}
}
41 changes: 38 additions & 3 deletions Debugger/tests/Unit/VariableTableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,11 @@ public function testRegistersSimilarObjects()

public function testRegistersArrayAsSameObjects()
{
$this->markTestSkipped('Array deduping NYI');
$variableTable = new VariableTable();
$object = ['abc', 123];

$variable1 = $variableTable->register('int', $object);
$variable2 = $variableTable->register('int2', $object);
$variable1 = $variableTable->register('int', $object, 'hashid');
$variable2 = $variableTable->register('int2', $object, 'hashid');

$data1 = $variable1;
$this->assertEquals(0, $data1->jsonSerialize()['varTableIndex']);
Expand Down Expand Up @@ -135,4 +134,40 @@ public function primitiveValues()
[NULL, 'NULL', 'NULL']
];
}

public function testRegisterObjectWithId()
{
$variableTable = new VariableTable();
$object = new Int64('123');
$object2 = new Int64('123');

$variable1 = $variableTable->register('int', $object, 'hashid');
$variable2 = $variableTable->register('int2', $object2, 'hashid');

$data1 = $variable1;
$this->assertEquals(0, $data1->jsonSerialize()['varTableIndex']);

$data2 = $variable2;
$this->assertEquals(0, $data2->jsonSerialize()['varTableIndex']);

$this->assertCount(1, $variableTable->variables());
}

public function testRegisterSameStrings()
{
$variableTable = new VariableTable();
$string = 'Hello world';
$string2 = 'Hello world';

$variable1 = $variableTable->register('int', $string, 'hashid');
$variable2 = $variableTable->register('int2', $string2, 'hashid');

$data1 = $variable1;
$this->assertEquals(0, $data1->jsonSerialize()['varTableIndex']);

$data2 = $variable2;
$this->assertEquals(0, $data2->jsonSerialize()['varTableIndex']);

$this->assertCount(1, $variableTable->variables());
}
}

0 comments on commit 1f10fab

Please sign in to comment.