File tree Expand file tree Collapse file tree 8 files changed +176
-3
lines changed
tests/PHPStan/Rules/Functions Expand file tree Collapse file tree 8 files changed +176
-3
lines changed Original file line number Diff line number Diff line change 3737 - PHPStan\Rules\Functions\ExistingClassesInTypehintsRule
3838 - PHPStan\Rules\Functions\InnerFunctionRule
3939 - PHPStan\Rules\Functions\PrintfParametersRule
40+ - PHPStan\Rules\Functions\ReturnNullsafeByRefRule
4041 - PHPStan\Rules\Methods\AbstractMethodInNonAbstractClassRule
4142 - PHPStan\Rules\Methods\ExistingClassesInTypehintsRule
4243 - PHPStan\Rules\Methods\MissingMethodImplementationRule
Original file line number Diff line number Diff line change 66use PhpParser \NodeAbstract ;
77use PHPStan \Analyser \StatementResult ;
88
9- class ClosureReturnStatementsNode extends NodeAbstract implements VirtualNode
9+ class ClosureReturnStatementsNode extends NodeAbstract implements ReturnStatementsNode
1010{
1111
1212 private \PhpParser \Node \Expr \Closure $ closureExpr ;
@@ -51,6 +51,11 @@ public function getStatementResult(): StatementResult
5151 return $ this ->statementResult ;
5252 }
5353
54+ public function returnsByRef (): bool
55+ {
56+ return $ this ->closureExpr ->byRef ;
57+ }
58+
5459 public function getType (): string
5560 {
5661 return 'PHPStan_Node_ClosureReturnStatementsNode ' ;
Original file line number Diff line number Diff line change 66use PhpParser \NodeAbstract ;
77use PHPStan \Analyser \StatementResult ;
88
9- class FunctionReturnStatementsNode extends NodeAbstract implements VirtualNode
9+ class FunctionReturnStatementsNode extends NodeAbstract implements ReturnStatementsNode
1010{
1111
12+ private Function_ $ function ;
13+
1214 /** @var \PHPStan\Node\ReturnStatement[] */
1315 private array $ returnStatements ;
1416
@@ -26,6 +28,7 @@ public function __construct(
2628 )
2729 {
2830 parent ::__construct ($ function ->getAttributes ());
31+ $ this ->function = $ function ;
2932 $ this ->returnStatements = $ returnStatements ;
3033 $ this ->statementResult = $ statementResult ;
3134 }
@@ -43,6 +46,11 @@ public function getStatementResult(): StatementResult
4346 return $ this ->statementResult ;
4447 }
4548
49+ public function returnsByRef (): bool
50+ {
51+ return $ this ->function ->byRef ;
52+ }
53+
4654 public function getType (): string
4755 {
4856 return 'PHPStan_Node_FunctionReturnStatementsNode ' ;
Original file line number Diff line number Diff line change 66use PhpParser \NodeAbstract ;
77use PHPStan \Analyser \StatementResult ;
88
9- class MethodReturnStatementsNode extends NodeAbstract implements VirtualNode
9+ class MethodReturnStatementsNode extends NodeAbstract implements ReturnStatementsNode
1010{
1111
12+ private ClassMethod $ classMethod ;
13+
1214 /** @var \PHPStan\Node\ReturnStatement[] */
1315 private array $ returnStatements ;
1416
@@ -26,6 +28,7 @@ public function __construct(
2628 )
2729 {
2830 parent ::__construct ($ method ->getAttributes ());
31+ $ this ->classMethod = $ method ;
2932 $ this ->returnStatements = $ returnStatements ;
3033 $ this ->statementResult = $ statementResult ;
3134 }
@@ -43,6 +46,11 @@ public function getStatementResult(): StatementResult
4346 return $ this ->statementResult ;
4447 }
4548
49+ public function returnsByRef (): bool
50+ {
51+ return $ this ->classMethod ->byRef ;
52+ }
53+
4654 public function getType (): string
4755 {
4856 return 'PHPStan_Node_FunctionReturnStatementsNode ' ;
Original file line number Diff line number Diff line change 1+ <?php declare (strict_types = 1 );
2+
3+ namespace PHPStan \Node ;
4+
5+ use PHPStan \Analyser \StatementResult ;
6+
7+ interface ReturnStatementsNode extends VirtualNode
8+ {
9+
10+ /**
11+ * @return \PHPStan\Node\ReturnStatement[]
12+ */
13+ public function getReturnStatements (): array ;
14+
15+ public function getStatementResult (): StatementResult ;
16+
17+ public function returnsByRef (): bool ;
18+
19+ }
Original file line number Diff line number Diff line change 1+ <?php declare (strict_types = 1 );
2+
3+ namespace PHPStan \Rules \Functions ;
4+
5+ use PhpParser \Node ;
6+ use PHPStan \Analyser \Scope ;
7+ use PHPStan \Node \ReturnStatementsNode ;
8+ use PHPStan \Rules \NullsafeCheck ;
9+ use PHPStan \Rules \Rule ;
10+ use PHPStan \Rules \RuleErrorBuilder ;
11+
12+ /**
13+ * @implements Rule<ReturnStatementsNode>
14+ */
15+ class ReturnNullsafeByRefRule implements Rule
16+ {
17+
18+ private NullsafeCheck $ nullsafeCheck ;
19+
20+ public function __construct (NullsafeCheck $ nullsafeCheck )
21+ {
22+ $ this ->nullsafeCheck = $ nullsafeCheck ;
23+ }
24+
25+ public function getNodeType (): string
26+ {
27+ return ReturnStatementsNode::class;
28+ }
29+
30+ public function processNode (Node $ node , Scope $ scope ): array
31+ {
32+ if (!$ node ->returnsByRef ()) {
33+ return [];
34+ }
35+
36+ $ errors = [];
37+ foreach ($ node ->getReturnStatements () as $ returnStatement ) {
38+ $ returnNode = $ returnStatement ->getReturnNode ();
39+ if ($ returnNode ->expr === null ) {
40+ continue ;
41+ }
42+
43+ if (!$ this ->nullsafeCheck ->containsNullSafe ($ returnNode ->expr )) {
44+ continue ;
45+ }
46+
47+ $ errors [] = RuleErrorBuilder::message ('Nullsafe cannot be returned by reference. ' )->line ($ returnNode ->getLine ())->nonIgnorable ()->build ();
48+ }
49+
50+ return $ errors ;
51+ }
52+
53+ }
Original file line number Diff line number Diff line change 1+ <?php declare (strict_types = 1 );
2+
3+ namespace PHPStan \Rules \Functions ;
4+
5+ use PHPStan \Rules \NullsafeCheck ;
6+ use PHPStan \Rules \Rule ;
7+ use PHPStan \Testing \RuleTestCase ;
8+
9+ /**
10+ * @extends RuleTestCase<ReturnNullsafeByRefRule>
11+ */
12+ class ReturnNullsafeByRefRuleTest extends RuleTestCase
13+ {
14+
15+ protected function getRule (): Rule
16+ {
17+ return new ReturnNullsafeByRefRule (new NullsafeCheck ());
18+ }
19+
20+ public function testRule (): void
21+ {
22+ if (!self ::$ useStaticReflectionProvider ) {
23+ $ this ->markTestSkipped ('Test requires static reflection. ' );
24+ }
25+
26+ $ this ->analyse ([__DIR__ . '/data/return-null-safe-by-ref.php ' ], [
27+ [
28+ 'Nullsafe cannot be returned by reference. ' ,
29+ 15 ,
30+ ],
31+ [
32+ 'Nullsafe cannot be returned by reference. ' ,
33+ 25 ,
34+ ],
35+ [
36+ 'Nullsafe cannot be returned by reference. ' ,
37+ 36 ,
38+ ],
39+ ]);
40+ }
41+
42+ }
Original file line number Diff line number Diff line change 1+ <?php
2+
3+ namespace ReturnNullsafeByRef ;
4+
5+ class Foo
6+ {
7+
8+ public function doFoo (?\stdClass $ foo )
9+ {
10+ function &() use ($ foo ) {
11+ if (rand (0 , 1 )) {
12+ return $ foo ;
13+ }
14+
15+ return $ foo ?->bar->foo ;
16+ };
17+ }
18+
19+ public function &doBar ()
20+ {
21+ if (rand (0 , 1 )) {
22+ return $ foo ;
23+ }
24+
25+ return $ foo ?->bar->foo ;
26+ }
27+
28+ }
29+
30+ function &foo (): void
31+ {
32+ if (rand (0 , 1 )) {
33+ return $ foo ;
34+ }
35+
36+ return $ foo ?->bar->foo ;
37+ }
You can’t perform that action at this time.
0 commit comments