NodeVisitor::addUseNameToBlackList()   C
last analyzed

Complexity

Conditions 12
Paths 81

Size

Total Lines 32

Duplication

Lines 18
Ratio 56.25 %

Importance

Changes 0
Metric Value
cc 12
nc 81
nop 2
dl 18
loc 32
rs 6.9666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Joas Schilling <[email protected]>
4
 * @author Morris Jobke <[email protected]>
5
 * @author Thomas Müller <[email protected]>
6
 *
7
 * @copyright Copyright (c) 2018, ownCloud GmbH
8
 * @license AGPL-3.0
9
 *
10
 * This code is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU Affero General Public License, version 3,
12
 * as published by the Free Software Foundation.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License, version 3,
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
21
 *
22
 */
23
24
namespace OC\App\CodeChecker;
25
26
use PhpParser\Node;
27
use PhpParser\Node\Name;
28
use PhpParser\NodeVisitorAbstract;
29
30
class NodeVisitor extends NodeVisitorAbstract {
31
	/** @var ICheck */
32
	protected $list;
33
34
	/** @var string */
35
	protected $blackListDescription;
36
	/** @var string[] */
37
	protected $blackListedClassNames;
38
	/** @var string[] */
39
	protected $blackListedConstants;
40
	/** @var string[] */
41
	protected $blackListedFunctions;
42
	/** @var string[] */
43
	protected $blackListedMethods;
44
	/** @var bool */
45
	protected $checkEqualOperatorUsage;
46
	/** @var string[] */
47
	protected $errorMessages;
48
49
	/**
50
	 * @param ICheck $list
51
	 */
52
	public function __construct(ICheck $list) {
53
		$this->list = $list;
54
55
		$this->blackListedClassNames = [];
56
		foreach ($list->getClasses() as $class => $blackListInfo) {
57
			if (\is_numeric($class) && \is_string($blackListInfo)) {
58
				$class = $blackListInfo;
59
				$blackListInfo = null;
60
			}
61
62
			$class = \strtolower($class);
63
			$this->blackListedClassNames[$class] = $class;
64
		}
65
66
		$this->blackListedConstants = [];
67
		foreach ($list->getConstants() as $constantName => $blackListInfo) {
68
			$constantName = \strtolower($constantName);
69
			$this->blackListedConstants[$constantName] = $constantName;
70
		}
71
72
		$this->blackListedFunctions = [];
73
		foreach ($list->getFunctions() as $functionName => $blackListInfo) {
74
			$functionName = \strtolower($functionName);
75
			$this->blackListedFunctions[$functionName] = $functionName;
76
		}
77
78
		$this->blackListedMethods = [];
79
		foreach ($list->getMethods() as $functionName => $blackListInfo) {
80
			$functionName = \strtolower($functionName);
81
			$this->blackListedMethods[$functionName] = $functionName;
82
		}
83
84
		$this->checkEqualOperatorUsage = $list->checkStrongComparisons();
85
86
		$this->errorMessages = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array(\OC\App\CodeChecke...ED => 'is discouraged') of type array<string|integer,string> is incompatible with the declared type array<integer,string> of property $errorMessages.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
87
			CodeChecker::CLASS_EXTENDS_NOT_ALLOWED => "%s class must not be extended",
88
			CodeChecker::CLASS_IMPLEMENTS_NOT_ALLOWED => "%s interface must not be implemented",
89
			CodeChecker::STATIC_CALL_NOT_ALLOWED => "Static method of %s class must not be called",
90
			CodeChecker::CLASS_CONST_FETCH_NOT_ALLOWED => "Constant of %s class must not not be fetched",
91
			CodeChecker::CLASS_NEW_NOT_ALLOWED => "%s class must not be instantiated",
92
			CodeChecker::CLASS_USE_NOT_ALLOWED => "%s class must not be imported with a use statement",
93
			CodeChecker::CLASS_METHOD_CALL_NOT_ALLOWED => "Method of %s class must not be called",
94
95
			CodeChecker::OP_OPERATOR_USAGE_DISCOURAGED => "is discouraged",
96
		];
97
	}
98
99
	/** @var array */
100
	public $errors = [];
101
102
	public function enterNode(Node $node) {
103 View Code Duplication
		if ($this->checkEqualOperatorUsage && $node instanceof Node\Expr\BinaryOp\Equal) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
104
			$this->errors[]= [
105
				'disallowedToken' => '==',
106
				'errorCode' => CodeChecker::OP_OPERATOR_USAGE_DISCOURAGED,
107
				'line' => $node->getLine(),
108
				'reason' => $this->buildReason('==', CodeChecker::OP_OPERATOR_USAGE_DISCOURAGED)
109
			];
110
		}
111 View Code Duplication
		if ($this->checkEqualOperatorUsage && $node instanceof Node\Expr\BinaryOp\NotEqual) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
112
			$this->errors[]= [
113
				'disallowedToken' => '!=',
114
				'errorCode' => CodeChecker::OP_OPERATOR_USAGE_DISCOURAGED,
115
				'line' => $node->getLine(),
116
				'reason' => $this->buildReason('!=', CodeChecker::OP_OPERATOR_USAGE_DISCOURAGED)
117
			];
118
		}
119
		if ($node instanceof Node\Stmt\Class_) {
120
			if ($node->extends !== null) {
121
				$this->checkBlackList($node->extends->toString(), CodeChecker::CLASS_EXTENDS_NOT_ALLOWED, $node);
122
			}
123
			foreach ($node->implements as $implements) {
124
				$this->checkBlackList($implements->toString(), CodeChecker::CLASS_IMPLEMENTS_NOT_ALLOWED, $node);
125
			}
126
		}
127
		if ($node instanceof Node\Expr\StaticCall) {
128
			if ($node->class !== null) {
129
				if ($node->class instanceof Name) {
130
					$this->checkBlackList($node->class->toString(), CodeChecker::STATIC_CALL_NOT_ALLOWED, $node);
0 ignored issues
show
Bug introduced by
The method toString does only exist in PhpParser\Node\Name, but not in PhpParser\Node\Expr.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
131
132
					$this->checkBlackListFunction($node->class->toString(), $node->name, $node);
133
					$this->checkBlackListMethod($node->class->toString(), $node->name, $node);
134
				}
135
136
				if ($node->class instanceof Node\Expr\Variable) {
137
					/**
138
					 * TODO: find a way to detect something like this:
139
					 *       $c = "OC_API";
140
					 *       $n = $c::call();
141
					 */
142
					// $this->checkBlackListMethod($node->class->..., $node->name, $node);
143
				}
144
			}
145
		}
146
		if ($node instanceof Node\Expr\MethodCall) {
147
			if ($node->var !== null) {
148
				if ($node->var instanceof Node\Expr\Variable) {
149
					/**
150
					 * TODO: find a way to detect something like this:
151
					 *       $c = new OC_API();
152
					 *       $n = $c::call();
153
					 *       $n = $c->call();
154
					 */
155
					// $this->checkBlackListMethod($node->var->..., $node->name, $node);
156
				}
157
			}
158
		}
159
		if ($node instanceof Node\Expr\ClassConstFetch) {
160
			if ($node->class !== null) {
161
				if ($node->class instanceof Name) {
162
					$this->checkBlackList($node->class->toString(), CodeChecker::CLASS_CONST_FETCH_NOT_ALLOWED, $node);
163
				}
164
				if ($node->class instanceof Node\Expr\Variable) {
165
					/**
166
					 * TODO: find a way to detect something like this:
167
					 *       $c = "OC_API";
168
					 *       $n = $i::ADMIN_AUTH;
169
					 */
170
				} else {
171
					$this->checkBlackListConstant($node->class->toString(), $node->name, $node);
172
				}
173
			}
174
		}
175
		if ($node instanceof Node\Expr\New_) {
176
			if ($node->class !== null) {
177
				if ($node->class instanceof Name) {
178
					$this->checkBlackList($node->class->toString(), CodeChecker::CLASS_NEW_NOT_ALLOWED, $node);
179
				}
180
				if ($node->class instanceof Node\Expr\Variable) {
181
					/**
182
					 * TODO: find a way to detect something like this:
183
					 *       $c = "OC_API";
184
					 *       $n = new $i;
185
					 */
186
				}
187
			}
188
		}
189
		if ($node instanceof Node\Stmt\UseUse) {
190
			$this->checkBlackList($node->name->toString(), CodeChecker::CLASS_USE_NOT_ALLOWED, $node);
191
			if ($node->alias) {
192
				$this->addUseNameToBlackList($node->name->toString(), $node->alias);
193
			} else {
194
				$this->addUseNameToBlackList($node->name->toString(), $node->name->getLast());
195
			}
196
		}
197
	}
198
199
	/**
200
	 * Check whether an alias was introduced for a namespace of a blacklisted class
201
	 *
202
	 * Example:
203
	 * - Blacklist entry:      OCP\AppFramework\IApi
204
	 * - Name:                 OCP\AppFramework
205
	 * - Alias:                OAF
206
	 * =>  new blacklist entry:  OAF\IApi
207
	 *
208
	 * @param string $name
209
	 * @param string $alias
210
	 */
211
	private function addUseNameToBlackList($name, $alias) {
212
		$name = \strtolower($name);
213
		$alias = \strtolower($alias);
214
215
		foreach ($this->blackListedClassNames as $blackListedAlias => $blackListedClassName) {
216
			if (\strpos($blackListedClassName, $name . '\\') === 0) {
217
				$aliasedClassName = \str_replace($name, $alias, $blackListedClassName);
218
				$this->blackListedClassNames[$aliasedClassName] = $blackListedClassName;
219
			}
220
		}
221
222 View Code Duplication
		foreach ($this->blackListedConstants as $blackListedAlias => $blackListedConstant) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
223
			if (\strpos($blackListedConstant, $name . '\\') === 0 || \strpos($blackListedConstant, $name . '::') === 0) {
224
				$aliasedConstantName = \str_replace($name, $alias, $blackListedConstant);
225
				$this->blackListedConstants[$aliasedConstantName] = $blackListedConstant;
226
			}
227
		}
228
229 View Code Duplication
		foreach ($this->blackListedFunctions as $blackListedAlias => $blackListedFunction) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
230
			if (\strpos($blackListedFunction, $name . '\\') === 0 || \strpos($blackListedFunction, $name . '::') === 0) {
231
				$aliasedFunctionName = \str_replace($name, $alias, $blackListedFunction);
232
				$this->blackListedFunctions[$aliasedFunctionName] = $blackListedFunction;
233
			}
234
		}
235
236 View Code Duplication
		foreach ($this->blackListedMethods as $blackListedAlias => $blackListedMethod) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
237
			if (\strpos($blackListedMethod, $name . '\\') === 0 || \strpos($blackListedMethod, $name . '::') === 0) {
238
				$aliasedMethodName = \str_replace($name, $alias, $blackListedMethod);
239
				$this->blackListedMethods[$aliasedMethodName] = $blackListedMethod;
240
			}
241
		}
242
	}
243
244
	private function checkBlackList($name, $errorCode, Node $node) {
245
		$lowerName = \strtolower($name);
246
247
		if (isset($this->blackListedClassNames[$lowerName])) {
248
			$this->errors[]= [
249
				'disallowedToken' => $name,
250
				'errorCode' => $errorCode,
251
				'line' => $node->getLine(),
252
				'reason' => $this->buildReason($this->blackListedClassNames[$lowerName], $errorCode)
253
			];
254
		}
255
	}
256
257 View Code Duplication
	private function checkBlackListConstant($class, $constantName, Node $node) {
258
		$name = $class . '::' . $constantName;
259
		$lowerName = \strtolower($name);
260
261
		if (isset($this->blackListedConstants[$lowerName])) {
262
			$this->errors[]= [
263
				'disallowedToken' => $name,
264
				'errorCode' => CodeChecker::CLASS_CONST_FETCH_NOT_ALLOWED,
265
				'line' => $node->getLine(),
266
				'reason' => $this->buildReason($this->blackListedConstants[$lowerName], CodeChecker::CLASS_CONST_FETCH_NOT_ALLOWED)
267
			];
268
		}
269
	}
270
271 View Code Duplication
	private function checkBlackListFunction($class, $functionName, Node $node) {
272
		$name = $class . '::' . $functionName;
273
		$lowerName = \strtolower($name);
274
275
		if (isset($this->blackListedFunctions[$lowerName])) {
276
			$this->errors[]= [
277
				'disallowedToken' => $name,
278
				'errorCode' => CodeChecker::STATIC_CALL_NOT_ALLOWED,
279
				'line' => $node->getLine(),
280
				'reason' => $this->buildReason($this->blackListedFunctions[$lowerName], CodeChecker::STATIC_CALL_NOT_ALLOWED)
281
			];
282
		}
283
	}
284
285 View Code Duplication
	private function checkBlackListMethod($class, $functionName, Node $node) {
286
		$name = $class . '::' . $functionName;
287
		$lowerName = \strtolower($name);
288
289
		if (isset($this->blackListedMethods[$lowerName])) {
290
			$this->errors[]= [
291
				'disallowedToken' => $name,
292
				'errorCode' => CodeChecker::CLASS_METHOD_CALL_NOT_ALLOWED,
293
				'line' => $node->getLine(),
294
				'reason' => $this->buildReason($this->blackListedMethods[$lowerName], CodeChecker::CLASS_METHOD_CALL_NOT_ALLOWED)
295
			];
296
		}
297
	}
298
299
	private function buildReason($name, $errorCode) {
300
		if (isset($this->errorMessages[$errorCode])) {
301
			$desc = $this->list->getDescription($errorCode, $name);
302
			return \sprintf($this->errorMessages[$errorCode], $desc);
303
		}
304
305
		return "$name usage not allowed - error: $errorCode";
306
	}
307
}
308