comparison vendor/nikic/php-parser/test/PhpParser/NodeTraverserTest.php @ 13:5fb285c0d0e3

Update Drupal core to 8.4.7 via Composer. Security update; I *think* we've been lucky to get away with this so far, as we don't support self-registration which seems to be used by the so-called "drupalgeddon 2" attack that 8.4.5 was vulnerable to.
author Chris Cannam
date Mon, 23 Apr 2018 09:33:26 +0100
parents 4c8ae668cc8c
children 129ea1e6d783
comparison
equal deleted inserted replaced
12:7a779792577d 13:5fb285c0d0e3
1 <?php 1 <?php declare(strict_types=1);
2 2
3 namespace PhpParser; 3 namespace PhpParser;
4 4
5 use PhpParser\Node\Expr; 5 use PhpParser\Node\Expr;
6 use PhpParser\Node\Scalar\String_; 6 use PhpParser\Node\Scalar\String_;
7 7 use PhpParser\NodeVisitor;
8 class NodeTraverserTest extends \PHPUnit_Framework_TestCase 8 use PHPUnit\Framework\TestCase;
9
10 class NodeTraverserTest extends TestCase
9 { 11 {
10 public function testNonModifying() { 12 public function testNonModifying() {
11 $str1Node = new String_('Foo'); 13 $str1Node = new String_('Foo');
12 $str2Node = new String_('Bar'); 14 $str2Node = new String_('Bar');
13 $echoNode = new Node\Stmt\Echo_(array($str1Node, $str2Node)); 15 $echoNode = new Node\Stmt\Echo_([$str1Node, $str2Node]);
14 $stmts = array($echoNode); 16 $stmts = [$echoNode];
15 17
16 $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 18 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
17 19
18 $visitor->expects($this->at(0))->method('beforeTraverse')->with($stmts); 20 $visitor->expects($this->at(0))->method('beforeTraverse')->with($stmts);
19 $visitor->expects($this->at(1))->method('enterNode')->with($echoNode); 21 $visitor->expects($this->at(1))->method('enterNode')->with($echoNode);
20 $visitor->expects($this->at(2))->method('enterNode')->with($str1Node); 22 $visitor->expects($this->at(2))->method('enterNode')->with($str1Node);
21 $visitor->expects($this->at(3))->method('leaveNode')->with($str1Node); 23 $visitor->expects($this->at(3))->method('leaveNode')->with($str1Node);
34 $str1Node = new String_('Foo'); 36 $str1Node = new String_('Foo');
35 $str2Node = new String_('Bar'); 37 $str2Node = new String_('Bar');
36 $printNode = new Expr\Print_($str1Node); 38 $printNode = new Expr\Print_($str1Node);
37 39
38 // first visitor changes the node, second verifies the change 40 // first visitor changes the node, second verifies the change
39 $visitor1 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 41 $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
40 $visitor2 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 42 $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
41 43
42 // replace empty statements with string1 node 44 // replace empty statements with string1 node
43 $visitor1->expects($this->at(0))->method('beforeTraverse')->with(array()) 45 $visitor1->expects($this->at(0))->method('beforeTraverse')->with([])
44 ->will($this->returnValue(array($str1Node))); 46 ->will($this->returnValue([$str1Node]));
45 $visitor2->expects($this->at(0))->method('beforeTraverse')->with(array($str1Node)); 47 $visitor2->expects($this->at(0))->method('beforeTraverse')->with([$str1Node]);
46 48
47 // replace string1 node with print node 49 // replace string1 node with print node
48 $visitor1->expects($this->at(1))->method('enterNode')->with($str1Node) 50 $visitor1->expects($this->at(1))->method('enterNode')->with($str1Node)
49 ->will($this->returnValue($printNode)); 51 ->will($this->returnValue($printNode));
50 $visitor2->expects($this->at(1))->method('enterNode')->with($printNode); 52 $visitor2->expects($this->at(1))->method('enterNode')->with($printNode);
63 $visitor1->expects($this->at(4))->method('leaveNode')->with($printNode) 65 $visitor1->expects($this->at(4))->method('leaveNode')->with($printNode)
64 ->will($this->returnValue($str1Node)); 66 ->will($this->returnValue($str1Node));
65 $visitor2->expects($this->at(4))->method('leaveNode')->with($str1Node); 67 $visitor2->expects($this->at(4))->method('leaveNode')->with($str1Node);
66 68
67 // replace string1 node with empty statements again 69 // replace string1 node with empty statements again
68 $visitor1->expects($this->at(5))->method('afterTraverse')->with(array($str1Node)) 70 $visitor1->expects($this->at(5))->method('afterTraverse')->with([$str1Node])
69 ->will($this->returnValue(array())); 71 ->will($this->returnValue([]));
70 $visitor2->expects($this->at(5))->method('afterTraverse')->with(array()); 72 $visitor2->expects($this->at(5))->method('afterTraverse')->with([]);
71 73
72 $traverser = new NodeTraverser; 74 $traverser = new NodeTraverser;
73 $traverser->addVisitor($visitor1); 75 $traverser->addVisitor($visitor1);
74 $traverser->addVisitor($visitor2); 76 $traverser->addVisitor($visitor2);
75 77
76 // as all operations are reversed we end where we start 78 // as all operations are reversed we end where we start
77 $this->assertEquals(array(), $traverser->traverse(array())); 79 $this->assertEquals([], $traverser->traverse([]));
78 } 80 }
79 81
80 public function testRemove() { 82 public function testRemove() {
81 $str1Node = new String_('Foo'); 83 $str1Node = new String_('Foo');
82 $str2Node = new String_('Bar'); 84 $str2Node = new String_('Bar');
83 85
84 $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 86 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
85 87
86 // remove the string1 node, leave the string2 node 88 // remove the string1 node, leave the string2 node
87 $visitor->expects($this->at(2))->method('leaveNode')->with($str1Node) 89 $visitor->expects($this->at(2))->method('leaveNode')->with($str1Node)
88 ->will($this->returnValue(false)); 90 ->will($this->returnValue(NodeTraverser::REMOVE_NODE));
89 91
90 $traverser = new NodeTraverser; 92 $traverser = new NodeTraverser;
91 $traverser->addVisitor($visitor); 93 $traverser->addVisitor($visitor);
92 94
93 $this->assertEquals(array($str2Node), $traverser->traverse(array($str1Node, $str2Node))); 95 $this->assertEquals([$str2Node], $traverser->traverse([$str1Node, $str2Node]));
94 } 96 }
95 97
96 public function testMerge() { 98 public function testMerge() {
97 $strStart = new String_('Start'); 99 $strStart = new String_('Start');
98 $strMiddle = new String_('End'); 100 $strMiddle = new String_('End');
99 $strEnd = new String_('Middle'); 101 $strEnd = new String_('Middle');
100 $strR1 = new String_('Replacement 1'); 102 $strR1 = new String_('Replacement 1');
101 $strR2 = new String_('Replacement 2'); 103 $strR2 = new String_('Replacement 2');
102 104
103 $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 105 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
104 106
105 // replace strMiddle with strR1 and strR2 by merge 107 // replace strMiddle with strR1 and strR2 by merge
106 $visitor->expects($this->at(4))->method('leaveNode')->with($strMiddle) 108 $visitor->expects($this->at(4))->method('leaveNode')->with($strMiddle)
107 ->will($this->returnValue(array($strR1, $strR2))); 109 ->will($this->returnValue([$strR1, $strR2]));
108 110
109 $traverser = new NodeTraverser; 111 $traverser = new NodeTraverser;
110 $traverser->addVisitor($visitor); 112 $traverser->addVisitor($visitor);
111 113
112 $this->assertEquals( 114 $this->assertEquals(
113 array($strStart, $strR1, $strR2, $strEnd), 115 [$strStart, $strR1, $strR2, $strEnd],
114 $traverser->traverse(array($strStart, $strMiddle, $strEnd)) 116 $traverser->traverse([$strStart, $strMiddle, $strEnd])
115 ); 117 );
116 } 118 }
117 119
118 public function testDeepArray() { 120 /**
121 * @expectedException \LogicException
122 * @expectedExceptionMessage Invalid node structure: Contains nested arrays
123 */
124 public function testInvalidDeepArray() {
119 $strNode = new String_('Foo'); 125 $strNode = new String_('Foo');
120 $stmts = array(array(array($strNode))); 126 $stmts = [[[$strNode]]];
121 127
122 $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 128 $traverser = new NodeTraverser;
123 $visitor->expects($this->at(1))->method('enterNode')->with($strNode);
124
125 $traverser = new NodeTraverser;
126 $traverser->addVisitor($visitor);
127
128 $this->assertEquals($stmts, $traverser->traverse($stmts)); 129 $this->assertEquals($stmts, $traverser->traverse($stmts));
129 } 130 }
130 131
131 public function testDontTraverseChildren() { 132 public function testDontTraverseChildren() {
132 $strNode = new String_('str'); 133 $strNode = new String_('str');
133 $printNode = new Expr\Print_($strNode); 134 $printNode = new Expr\Print_($strNode);
134 $varNode = new Expr\Variable('foo'); 135 $varNode = new Expr\Variable('foo');
135 $mulNode = new Expr\BinaryOp\Mul($varNode, $varNode); 136 $mulNode = new Expr\BinaryOp\Mul($varNode, $varNode);
136 $negNode = new Expr\UnaryMinus($mulNode); 137 $negNode = new Expr\UnaryMinus($mulNode);
137 $stmts = array($printNode, $negNode); 138 $stmts = [$printNode, $negNode];
138 139
139 $visitor1 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 140 $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
140 $visitor2 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 141 $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
141 142
142 $visitor1->expects($this->at(1))->method('enterNode')->with($printNode) 143 $visitor1->expects($this->at(1))->method('enterNode')->with($printNode)
143 ->will($this->returnValue(NodeTraverser::DONT_TRAVERSE_CHILDREN)); 144 ->will($this->returnValue(NodeTraverser::DONT_TRAVERSE_CHILDREN));
144 $visitor2->expects($this->at(1))->method('enterNode')->with($printNode); 145 $visitor2->expects($this->at(1))->method('enterNode')->with($printNode);
145 146
173 $mulNode = new Expr\BinaryOp\Mul($varNode1, $varNode2); 174 $mulNode = new Expr\BinaryOp\Mul($varNode1, $varNode2);
174 $printNode = new Expr\Print_($varNode3); 175 $printNode = new Expr\Print_($varNode3);
175 $stmts = [$mulNode, $printNode]; 176 $stmts = [$mulNode, $printNode];
176 177
177 // From enterNode() with array parent 178 // From enterNode() with array parent
178 $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 179 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
179 $visitor->expects($this->at(1))->method('enterNode')->with($mulNode) 180 $visitor->expects($this->at(1))->method('enterNode')->with($mulNode)
180 ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL)); 181 ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
181 $visitor->expects($this->at(2))->method('afterTraverse'); 182 $visitor->expects($this->at(2))->method('afterTraverse');
182 $traverser = new NodeTraverser; 183 $traverser = new NodeTraverser;
183 $traverser->addVisitor($visitor); 184 $traverser->addVisitor($visitor);
184 $this->assertEquals($stmts, $traverser->traverse($stmts)); 185 $this->assertEquals($stmts, $traverser->traverse($stmts));
185 186
186 // From enterNode with Node parent 187 // From enterNode with Node parent
187 $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 188 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
188 $visitor->expects($this->at(2))->method('enterNode')->with($varNode1) 189 $visitor->expects($this->at(2))->method('enterNode')->with($varNode1)
189 ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL)); 190 ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
190 $visitor->expects($this->at(3))->method('afterTraverse'); 191 $visitor->expects($this->at(3))->method('afterTraverse');
191 $traverser = new NodeTraverser; 192 $traverser = new NodeTraverser;
192 $traverser->addVisitor($visitor); 193 $traverser->addVisitor($visitor);
193 $this->assertEquals($stmts, $traverser->traverse($stmts)); 194 $this->assertEquals($stmts, $traverser->traverse($stmts));
194 195
195 // From leaveNode with Node parent 196 // From leaveNode with Node parent
196 $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 197 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
197 $visitor->expects($this->at(3))->method('leaveNode')->with($varNode1) 198 $visitor->expects($this->at(3))->method('leaveNode')->with($varNode1)
198 ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL)); 199 ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
199 $visitor->expects($this->at(4))->method('afterTraverse'); 200 $visitor->expects($this->at(4))->method('afterTraverse');
200 $traverser = new NodeTraverser; 201 $traverser = new NodeTraverser;
201 $traverser->addVisitor($visitor); 202 $traverser->addVisitor($visitor);
202 $this->assertEquals($stmts, $traverser->traverse($stmts)); 203 $this->assertEquals($stmts, $traverser->traverse($stmts));
203 204
204 // From leaveNode with array parent 205 // From leaveNode with array parent
205 $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 206 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
206 $visitor->expects($this->at(6))->method('leaveNode')->with($mulNode) 207 $visitor->expects($this->at(6))->method('leaveNode')->with($mulNode)
207 ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL)); 208 ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
208 $visitor->expects($this->at(7))->method('afterTraverse'); 209 $visitor->expects($this->at(7))->method('afterTraverse');
209 $traverser = new NodeTraverser; 210 $traverser = new NodeTraverser;
210 $traverser->addVisitor($visitor); 211 $traverser->addVisitor($visitor);
211 $this->assertEquals($stmts, $traverser->traverse($stmts)); 212 $this->assertEquals($stmts, $traverser->traverse($stmts));
212 213
213 // Check that pending array modifications are still carried out 214 // Check that pending array modifications are still carried out
214 $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 215 $visitor = $this->getMockBuilder(NodeVisitor::class)->getMock();
215 $visitor->expects($this->at(6))->method('leaveNode')->with($mulNode) 216 $visitor->expects($this->at(6))->method('leaveNode')->with($mulNode)
216 ->will($this->returnValue(NodeTraverser::REMOVE_NODE)); 217 ->will($this->returnValue(NodeTraverser::REMOVE_NODE));
217 $visitor->expects($this->at(7))->method('enterNode')->with($printNode) 218 $visitor->expects($this->at(7))->method('enterNode')->with($printNode)
218 ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL)); 219 ->will($this->returnValue(NodeTraverser::STOP_TRAVERSAL));
219 $visitor->expects($this->at(8))->method('afterTraverse'); 220 $visitor->expects($this->at(8))->method('afterTraverse');
222 $this->assertEquals([$printNode], $traverser->traverse($stmts)); 223 $this->assertEquals([$printNode], $traverser->traverse($stmts));
223 224
224 } 225 }
225 226
226 public function testRemovingVisitor() { 227 public function testRemovingVisitor() {
227 $visitor1 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 228 $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
228 $visitor2 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 229 $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
229 $visitor3 = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 230 $visitor3 = $this->getMockBuilder(NodeVisitor::class)->getMock();
230 231
231 $traverser = new NodeTraverser; 232 $traverser = new NodeTraverser;
232 $traverser->addVisitor($visitor1); 233 $traverser->addVisitor($visitor1);
233 $traverser->addVisitor($visitor2); 234 $traverser->addVisitor($visitor2);
234 $traverser->addVisitor($visitor3); 235 $traverser->addVisitor($visitor3);
235 236
236 $preExpected = array($visitor1, $visitor2, $visitor3); 237 $preExpected = [$visitor1, $visitor2, $visitor3];
237 $this->assertAttributeSame($preExpected, 'visitors', $traverser, 'The appropriate visitors have not been added'); 238 $this->assertAttributeSame($preExpected, 'visitors', $traverser, 'The appropriate visitors have not been added');
238 239
239 $traverser->removeVisitor($visitor2); 240 $traverser->removeVisitor($visitor2);
240 241
241 $postExpected = array(0 => $visitor1, 2 => $visitor3); 242 $postExpected = [0 => $visitor1, 2 => $visitor3];
242 $this->assertAttributeSame($postExpected, 'visitors', $traverser, 'The appropriate visitors are not present after removal'); 243 $this->assertAttributeSame($postExpected, 'visitors', $traverser, 'The appropriate visitors are not present after removal');
243 } 244 }
244 245
245 public function testNoCloneNodes() { 246 public function testNoCloneNodes() {
246 $stmts = array(new Node\Stmt\Echo_(array(new String_('Foo'), new String_('Bar')))); 247 $stmts = [new Node\Stmt\Echo_([new String_('Foo'), new String_('Bar')])];
247 248
248 $traverser = new NodeTraverser; 249 $traverser = new NodeTraverser;
249 250
250 $this->assertSame($stmts, $traverser->traverse($stmts)); 251 $this->assertSame($stmts, $traverser->traverse($stmts));
251 } 252 }
252 253
253 /** 254 /**
254 * @expectedException \LogicException 255 * @dataProvider provideTestInvalidReturn
255 * @expectedExceptionMessage leaveNode() may only return an array if the parent structure is an array
256 */ 256 */
257 public function testReplaceByArrayOnlyAllowedIfParentIsArray() { 257 public function testInvalidReturn($visitor, $message) {
258 $stmts = array(new Node\Expr\UnaryMinus(new Node\Scalar\LNumber(42))); 258 $this->expectException(\LogicException::class);
259 259 $this->expectExceptionMessage($message);
260 $visitor = $this->getMockBuilder('PhpParser\NodeVisitor')->getMock(); 260
261 $visitor->method('leaveNode')->willReturn(array(new Node\Scalar\DNumber(42.0))); 261 $stmts = [new Node\Stmt\Expression(new Node\Scalar\LNumber(42))];
262 262
263 $traverser = new NodeTraverser(); 263 $traverser = new NodeTraverser();
264 $traverser->addVisitor($visitor); 264 $traverser->addVisitor($visitor);
265 $traverser->traverse($stmts); 265 $traverser->traverse($stmts);
266 } 266 }
267
268 public function provideTestInvalidReturn() {
269 $visitor1 = $this->getMockBuilder(NodeVisitor::class)->getMock();
270 $visitor1->expects($this->at(1))->method('enterNode')
271 ->willReturn('foobar');
272
273 $visitor2 = $this->getMockBuilder(NodeVisitor::class)->getMock();
274 $visitor2->expects($this->at(2))->method('enterNode')
275 ->willReturn('foobar');
276
277 $visitor3 = $this->getMockBuilder(NodeVisitor::class)->getMock();
278 $visitor3->expects($this->at(3))->method('leaveNode')
279 ->willReturn('foobar');
280
281 $visitor4 = $this->getMockBuilder(NodeVisitor::class)->getMock();
282 $visitor4->expects($this->at(4))->method('leaveNode')
283 ->willReturn('foobar');
284
285 $visitor5 = $this->getMockBuilder(NodeVisitor::class)->getMock();
286 $visitor5->expects($this->at(3))->method('leaveNode')
287 ->willReturn([new Node\Scalar\DNumber(42.0)]);
288
289 $visitor6 = $this->getMockBuilder(NodeVisitor::class)->getMock();
290 $visitor6->expects($this->at(4))->method('leaveNode')
291 ->willReturn(false);
292
293 $visitor7 = $this->getMockBuilder(NodeVisitor::class)->getMock();
294 $visitor7->expects($this->at(1))->method('enterNode')
295 ->willReturn(new Node\Scalar\LNumber(42));
296
297 $visitor8 = $this->getMockBuilder(NodeVisitor::class)->getMock();
298 $visitor8->expects($this->at(2))->method('enterNode')
299 ->willReturn(new Node\Stmt\Return_());
300
301 return [
302 [$visitor1, 'enterNode() returned invalid value of type string'],
303 [$visitor2, 'enterNode() returned invalid value of type string'],
304 [$visitor3, 'leaveNode() returned invalid value of type string'],
305 [$visitor4, 'leaveNode() returned invalid value of type string'],
306 [$visitor5, 'leaveNode() may only return an array if the parent structure is an array'],
307 [$visitor6, 'bool(false) return from leaveNode() no longer supported. Return NodeTraverser::REMOVE_NODE instead'],
308 [$visitor7, 'Trying to replace statement (Stmt_Expression) with expression (Scalar_LNumber). Are you missing a Stmt_Expression wrapper?'],
309 [$visitor8, 'Trying to replace expression (Scalar_LNumber) with statement (Stmt_Return)'],
310 ];
311 }
267 } 312 }