This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * \AppserverIo\Doppelgaenger\Parser\AnnotationParser |
||
5 | * |
||
6 | * NOTICE OF LICENSE |
||
7 | * |
||
8 | * This source file is subject to the Open Software License (OSL 3.0) |
||
9 | * that is available through the world-wide-web at this URL: |
||
10 | * http://opensource.org/licenses/osl-3.0.php |
||
11 | * |
||
12 | * PHP version 5 |
||
13 | * |
||
14 | * @author Bernhard Wick <[email protected]> |
||
15 | * @copyright 2015 TechDivision GmbH - <[email protected]> |
||
16 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) |
||
17 | * @link https://github.com/appserver-io/doppelgaenger |
||
18 | * @link http://www.appserver.io/ |
||
19 | */ |
||
20 | |||
21 | namespace AppserverIo\Doppelgaenger\Parser; |
||
22 | |||
23 | use AppserverIo\Doppelgaenger\Entities\Assertions\AssertionFactory; |
||
24 | use AppserverIo\Doppelgaenger\Entities\Assertions\RawAssertion; |
||
25 | use AppserverIo\Doppelgaenger\Entities\Assertions\TypedCollectionAssertion; |
||
26 | use AppserverIo\Doppelgaenger\Entities\Definitions\AttributeDefinition; |
||
27 | use AppserverIo\Doppelgaenger\Entities\Definitions\FunctionDefinition; |
||
28 | use AppserverIo\Doppelgaenger\Entities\Joinpoint; |
||
29 | use AppserverIo\Doppelgaenger\Entities\Lists\AssertionList; |
||
30 | use AppserverIo\Doppelgaenger\Entities\Assertions\ChainedAssertion; |
||
31 | use AppserverIo\Doppelgaenger\Config; |
||
32 | use AppserverIo\Doppelgaenger\Entities\Lists\PointcutExpressionList; |
||
33 | use AppserverIo\Doppelgaenger\Entities\PointcutExpression; |
||
34 | use AppserverIo\Doppelgaenger\Exceptions\ParserException; |
||
35 | use AppserverIo\Psr\MetaobjectProtocol\Dbc\Assertions\AssertionInterface; |
||
36 | use AppserverIo\Doppelgaenger\Interfaces\PropertiedStructureInterface; |
||
37 | use AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface; |
||
38 | use AppserverIo\Doppelgaenger\Dictionaries\ReservedKeywords; |
||
39 | use AppserverIo\Psr\MetaobjectProtocol\Aop\Annotations\Introduce; |
||
40 | use AppserverIo\Psr\MetaobjectProtocol\Dbc\Annotations\Ensures; |
||
41 | use AppserverIo\Psr\MetaobjectProtocol\Dbc\Annotations\Invariant; |
||
42 | use AppserverIo\Psr\MetaobjectProtocol\Dbc\Annotations\Requires; |
||
43 | use Herrera\Annotations\Tokenizer; |
||
44 | use Herrera\Annotations\Tokens; |
||
45 | use Herrera\Annotations\Convert\ToArray; |
||
46 | |||
47 | /** |
||
48 | * The AnnotationParser class which is used to get all usable parts from within DocBlock annotation |
||
49 | * |
||
50 | * @author Bernhard Wick <[email protected]> |
||
51 | * @copyright 2015 TechDivision GmbH - <[email protected]> |
||
52 | * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) |
||
53 | * @link https://github.com/appserver-io/doppelgaenger |
||
54 | * @link http://www.appserver.io/ |
||
55 | */ |
||
56 | class AnnotationParser extends AbstractParser |
||
57 | { |
||
58 | /** |
||
59 | * The configuration aspect we need here |
||
60 | * |
||
61 | * @var \AppserverIo\Doppelgaenger\Config $config |
||
62 | */ |
||
63 | protected $config; |
||
64 | |||
65 | /** |
||
66 | * The annotations which the parser will look for |
||
67 | * |
||
68 | * @var string[] $searchedAnnotations |
||
69 | */ |
||
70 | protected $searchedAnnotations; |
||
71 | |||
72 | /** |
||
73 | * All valid annotation types we consider complex |
||
74 | * |
||
75 | * @var string[] $validComplexAnnotations |
||
76 | */ |
||
77 | protected $validComplexAnnotations = array( |
||
78 | Ensures::ANNOTATION, |
||
79 | Invariant::ANNOTATION, |
||
80 | Requires::ANNOTATION, |
||
81 | Introduce::ANNOTATION |
||
82 | ); |
||
83 | |||
84 | /** |
||
85 | * Default constructor |
||
86 | * |
||
87 | * @param string $file The path of the file we want to parse |
||
88 | * @param \AppserverIo\Doppelgaenger\Config $config Configuration |
||
89 | * @param array $tokens The array of tokens taken from the file |
||
90 | * @param \AppserverIo\Doppelgaenger\Interfaces\StructureDefinitionInterface|null $currentDefinition The current definition we are working on |
||
91 | */ |
||
92 | public function __construct( |
||
93 | $file, |
||
94 | Config $config, |
||
95 | array & $tokens = array(), |
||
96 | StructureDefinitionInterface $currentDefinition = null |
||
97 | ) { |
||
98 | $this->config = $config; |
||
99 | |||
100 | parent::__construct($file, $config, null, null, $currentDefinition, $tokens); |
||
101 | } |
||
102 | |||
103 | /** |
||
104 | * Will add an annotation which the parser will then look for on its next run |
||
105 | * |
||
106 | * @param string $annotationString The basic annotation to search for |
||
107 | * |
||
108 | * @return null|false |
||
109 | */ |
||
110 | public function addAnnotation($annotationString) |
||
111 | { |
||
112 | // we rely on a leading "@" symbol, so sanitize the input |
||
113 | if (!is_string($annotationString)) { |
||
114 | return false; |
||
115 | |||
0 ignored issues
–
show
Coding Style
introduced
by
![]() |
|||
116 | } elseif (substr($annotationString, 0, 1) === '@') { |
||
117 | $annotationString = '@' . $annotationString; |
||
118 | } |
||
119 | |||
120 | $this->searchedAnnotations[] = $annotationString; |
||
121 | } |
||
122 | |||
123 | /** |
||
124 | * Will add an array of annotations which the parser will then look for on its next run |
||
125 | * |
||
126 | * @param string[] $annotationStrings The basic annotation to search for |
||
127 | * |
||
128 | * @return null |
||
129 | */ |
||
130 | public function addAnnotations(array $annotationStrings) |
||
131 | { |
||
132 | foreach ($annotationStrings as $annotationString) { |
||
133 | $this->addAnnotation($annotationString); |
||
134 | } |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | * Will return an array containing all annotations of a certain type which where found within a given string |
||
139 | * DocBlock syntax is preferred |
||
140 | * |
||
141 | * @param string $string String to search in |
||
142 | * @param string $annotationType Name of the annotation (without the leading "@") to search for |
||
143 | * |
||
144 | * @return \stdClass[] |
||
145 | * @throws \AppserverIo\Doppelgaenger\Exceptions\ParserException |
||
146 | */ |
||
147 | public function getAnnotationsByType($string, $annotationType) |
||
148 | { |
||
149 | $collectedAnnotations = array(); |
||
150 | |||
151 | // we have to determine what type of annotations we are searching for, complex (doctrine style) or simple |
||
152 | if (isset(array_flip($this->validComplexAnnotations)[$annotationType])) { |
||
153 | // complex annotations are parsed using herrera-io/php-annotations |
||
154 | |||
155 | // get our tokenizer and parse the doc Block |
||
156 | $tokenizer = new Tokenizer(); |
||
157 | $tokens = new Tokens($tokenizer->parse($string)); |
||
158 | |||
159 | // convert to array and run it through our advice factory |
||
160 | $toArray = new ToArray(); |
||
161 | $annotations = $toArray->convert($tokens); |
||
162 | |||
163 | // only collect annotations we want |
||
164 | foreach ($annotations as $annotation) { |
||
165 | if ($annotation->name === $annotationType) { |
||
166 | $collectedAnnotations[] = $annotation; |
||
167 | } |
||
168 | } |
||
169 | |||
0 ignored issues
–
show
|
|||
170 | } else { |
||
171 | // all other annotations we would like to parse ourselves |
||
172 | |||
173 | $rawAnnotations = array(); |
||
174 | preg_match_all('/@' . $annotationType . '.+?\n/s', $string, $rawAnnotations); |
||
175 | |||
176 | // build up stdClass instances from the result |
||
177 | foreach ($rawAnnotations[0] as $rawAnnotation) { |
||
178 | $annotationPieces = explode('##', preg_replace('/\s+/', '##', $rawAnnotation)); |
||
179 | |||
180 | // short sanity check |
||
181 | if ($annotationPieces[0] === '@' . $annotationType && |
||
182 | is_string($annotationPieces[1]) && |
||
183 | (is_string($annotationPieces[2]) || $annotationType === 'return') |
||
184 | ) { |
||
185 | // we got at least the pieces we are searching for, but we do not care about meaning here |
||
186 | |||
187 | // create the class and fill it |
||
188 | $annotation = new \stdClass(); |
||
189 | $annotation->name = $annotationType; |
||
190 | $annotation->values = array( |
||
191 | 'operand' => empty($annotationPieces[2]) ? '' : $annotationPieces[2], |
||
192 | 'typeHint' => $annotationPieces[1] |
||
193 | ); |
||
194 | |||
195 | $collectedAnnotations[] = $annotation; |
||
196 | |||
0 ignored issues
–
show
|
|||
197 | } else { |
||
198 | // tell them we got a problem |
||
199 | |||
200 | throw new ParserException( |
||
201 | sprintf( |
||
202 | 'Could not parse annotation %s within structure %s', |
||
203 | $rawAnnotation, |
||
204 | $this->currentDefinition->getQualifiedName() |
||
205 | ) |
||
206 | ); |
||
207 | } |
||
208 | } |
||
209 | } |
||
210 | |||
211 | return $collectedAnnotations; |
||
212 | } |
||
213 | |||
214 | /** |
||
215 | * Will return one pointcut which does specifically only match the joinpoints of the structure |
||
216 | * which this docblock belongs to |
||
217 | * |
||
218 | * @param string $docBlock The DocBlock to search in |
||
219 | * @param string $targetType Type of the target any resulting joinpoints have, e.g. Joinpoint::TARGET_METHOD |
||
220 | * @param string $targetName Name of the target any resulting joinpoints have |
||
221 | * |
||
222 | * @return \AppserverIo\Doppelgaenger\Entities\Lists\PointcutExpressionList |
||
223 | */ |
||
224 | public function getPointcutExpressions($docBlock, $targetType, $targetName) |
||
225 | { |
||
226 | $pointcutExpressions = new PointcutExpressionList(); |
||
227 | |||
228 | // get our tokenizer and parse the doc Block |
||
229 | $tokenizer = new Tokenizer(); |
||
230 | |||
231 | $tokenizer->ignore( |
||
232 | array( |
||
233 | 'param', |
||
234 | 'return', |
||
235 | 'throws' |
||
236 | ) |
||
237 | ); |
||
238 | $tokens = new Tokens($tokenizer->parse($docBlock)); |
||
239 | |||
240 | // convert to array and run it through our advice factory |
||
241 | $toArray = new ToArray(); |
||
242 | $annotations = $toArray->convert($tokens); |
||
243 | |||
244 | // create the entities for the join-points and advices the pointcut describes |
||
245 | foreach ($annotations as $annotation) { |
||
246 | // filter out the annotations which are no proper join-points |
||
247 | if (!class_exists('\AppserverIo\Psr\MetaobjectProtocol\Aop\Annotations\Advices\\' . $annotation->name)) { |
||
248 | continue; |
||
249 | } |
||
250 | |||
251 | // build the join-point |
||
252 | $joinpoint = new Joinpoint(); |
||
253 | $joinpoint->setTarget($targetType); |
||
254 | $joinpoint->setCodeHook($annotation->name); |
||
255 | $joinpoint->setStructure($this->currentDefinition->getQualifiedName()); |
||
256 | $joinpoint->setTargetName($targetName); |
||
257 | |||
258 | // build the pointcut(s) |
||
259 | foreach ($annotation->values as $rawAdvice) { |
||
260 | // as it might be an array we have to sanitize it first |
||
261 | if (!is_array($rawAdvice)) { |
||
262 | $rawAdvice = array($rawAdvice); |
||
263 | } |
||
264 | foreach ($rawAdvice as $adviceString) { |
||
265 | // create the pointcut |
||
266 | $pointcutExpression = new PointcutExpression($adviceString); |
||
267 | $pointcutExpression->setJoinpoint($joinpoint); |
||
268 | |||
269 | $pointcutExpressions->add($pointcutExpression); |
||
270 | } |
||
271 | } |
||
272 | } |
||
273 | |||
274 | return $pointcutExpressions; |
||
275 | } |
||
276 | |||
277 | /** |
||
278 | * Will get the conditions for a certain assertion indicating keyword like @requires or, if configured, @param |
||
279 | * |
||
280 | * @param string $docBlock The DocBlock to search in |
||
281 | * @param string $conditionKeyword The keyword we are searching for, use assertion defining tags here! |
||
282 | * @param boolean|null $privateContext If we have to mark the parsed annotations as having a private context |
||
283 | * as we would have trouble finding out for ourselves. |
||
284 | * |
||
285 | * @return boolean|\AppserverIo\Doppelgaenger\Entities\Lists\AssertionList |
||
286 | */ |
||
287 | public function getConditions($docBlock, $conditionKeyword, $privateContext = null) |
||
288 | { |
||
289 | // There are only 3 valid condition types |
||
290 | if ($conditionKeyword !== Requires::ANNOTATION && $conditionKeyword !== Ensures::ANNOTATION |
||
291 | && $conditionKeyword !== Invariant::ANNOTATION |
||
292 | ) { |
||
293 | return false; |
||
294 | } |
||
295 | |||
296 | // get the annotations for the passed condition keyword |
||
297 | $annotations = $this->getAnnotationsByType($docBlock, $conditionKeyword); |
||
298 | |||
299 | // if we have to enforce basic type safety we need some more annotations |
||
300 | if ($this->config->getValue('enforcement/enforce-default-type-safety') === true) { |
||
301 | // lets switch the |
||
302 | |||
303 | switch ($conditionKeyword) { |
||
304 | case Ensures::ANNOTATION: |
||
305 | // we have to consider @return annotations as well |
||
306 | |||
307 | $annotations = array_merge( |
||
308 | $annotations, |
||
309 | $this->getAnnotationsByType($docBlock, 'return') |
||
310 | ); |
||
311 | break; |
||
312 | |||
313 | case Requires::ANNOTATION: |
||
314 | // we have to consider @param annotations as well |
||
315 | |||
316 | $annotations = array_merge( |
||
317 | $annotations, |
||
318 | $this->getAnnotationsByType($docBlock, 'param') |
||
319 | ); |
||
320 | break; |
||
321 | |||
322 | default: |
||
323 | break; |
||
324 | } |
||
325 | } |
||
326 | |||
327 | // lets build up the result array |
||
328 | $assertionFactory = new AssertionFactory(); |
||
329 | if ($this->currentDefinition) { |
||
330 | $assertionFactory->setCurrentDefinition($this->currentDefinition); |
||
331 | } |
||
332 | $result = new AssertionList(); |
||
333 | foreach ($annotations as $annotation) { |
||
334 | // try to create assertion instances for all annotations |
||
335 | try { |
||
336 | $assertion = $assertionFactory->getInstance($annotation); |
||
337 | |||
0 ignored issues
–
show
|
|||
338 | } catch (\Exception $e) { |
||
339 | error_log($e->getMessage()); |
||
340 | continue; |
||
341 | } |
||
342 | |||
343 | if ($assertion !== false) { |
||
344 | // Do we already got a private context we can set? If not we have to find out four ourselves |
||
345 | if ($privateContext !== null) { |
||
346 | // Add the context (wether private or not) |
||
347 | $assertion->setPrivateContext($privateContext); |
||
348 | |||
0 ignored issues
–
show
|
|||
349 | } else { |
||
350 | // Add the context (private or not) |
||
351 | $this->determinePrivateContext($assertion); |
||
0 ignored issues
–
show
It seems like
$assertion defined by $assertionFactory->getInstance($annotation) on line 336 can be null ; however, AppserverIo\Doppelgaenge...terminePrivateContext() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
352 | } |
||
353 | |||
354 | // finally determine the minimal scope of this assertion and add it to our result |
||
355 | $this->determineMinimalScope($assertion); |
||
0 ignored issues
–
show
It seems like
$assertion defined by $assertionFactory->getInstance($annotation) on line 336 can be null ; however, AppserverIo\Doppelgaenge...determineMinimalScope() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
356 | $result->add($assertion); |
||
357 | } |
||
358 | } |
||
359 | |||
360 | return $result; |
||
361 | } |
||
362 | |||
363 | /** |
||
364 | * Will filter all method calls from within the assertion string |
||
365 | * |
||
366 | * @param string $docString The DocBlock piece to search in |
||
367 | * |
||
368 | * @return array |
||
369 | */ |
||
370 | protected function filterMethodCalls($docString) |
||
371 | { |
||
372 | // We will be regex ninjas here |
||
373 | $results = array(); |
||
374 | preg_match_all('/->(.*?)\(/', $docString, $results); |
||
375 | |||
376 | // Return the clean output |
||
377 | return $results[1]; |
||
378 | } |
||
379 | |||
380 | /** |
||
381 | * Will filter all attributes which are used within an assertion string |
||
382 | * |
||
383 | * @param string $docString The DocBlock piece to search in |
||
384 | * |
||
385 | * @return array |
||
386 | */ |
||
387 | protected function filterAttributes($docString) |
||
388 | { |
||
389 | // We will be regex ninjas here |
||
390 | $tmp = array(); |
||
391 | preg_match_all('/(this->|self::)([a-zA-Z0-9_]*?)[=!\s<>,\)\[\]]/', $docString, $tmp); |
||
392 | |||
393 | $results = array(); |
||
394 | foreach ($tmp[2] as $rawAttribute) { |
||
395 | $results[] = '$' . $rawAttribute; |
||
396 | } |
||
397 | |||
398 | // Return the clean output |
||
399 | return $results; |
||
400 | } |
||
401 | |||
402 | /** |
||
403 | * Will try to figure out if the passed assertion has a private context or not. |
||
404 | * This information will be entered into the assertion which will then be returned. |
||
405 | * |
||
406 | * @param \AppserverIo\Psr\MetaobjectProtocol\Dbc\Assertions\AssertionInterface $assertion The assertion we need the context for |
||
407 | * |
||
408 | * @return void |
||
409 | */ |
||
410 | protected function determinePrivateContext(AssertionInterface $assertion) |
||
411 | { |
||
412 | // we only have to act if the current definition has functions and properties |
||
413 | if (!$this->currentDefinition instanceof PropertiedStructureInterface || !$this->currentDefinition instanceof StructureDefinitionInterface) { |
||
414 | return; |
||
415 | } |
||
416 | |||
417 | // Get the string to check for dynamic properties |
||
418 | $assertionString = $assertion->getString(); |
||
419 | |||
420 | // Do we have method calls? |
||
421 | $methodCalls = $this->filterMethodCalls($assertionString); |
||
422 | |||
423 | View Code Duplication | if (!empty($methodCalls)) { |
|
424 | // Iterate over all method calls and check if they are private |
||
425 | foreach ($methodCalls as $methodCall) { |
||
426 | // Get the function definition, but do not get recursive conditions |
||
427 | $functionDefinition = $this->currentDefinition->getFunctionDefinitions()->get($methodCall); |
||
428 | |||
429 | // If we found something private we can end here |
||
430 | if ($functionDefinition instanceof FunctionDefinition && |
||
431 | $functionDefinition->getVisibility() === 'private' |
||
432 | ) { |
||
433 | // Set the private context to true and return it |
||
434 | $assertion->setPrivateContext(true); |
||
435 | |||
436 | return; |
||
437 | } |
||
438 | } |
||
439 | } |
||
440 | |||
441 | // Do we have any attributes? |
||
442 | $attributes = $this->filterAttributes($assertionString); |
||
443 | |||
444 | View Code Duplication | if (!empty($attributes)) { |
|
445 | // Iterate over all attributes and check if they are private |
||
446 | foreach ($attributes as $attribute) { |
||
447 | $attributeDefinition = $this->currentDefinition->getAttributeDefinitions()->get($attribute); |
||
448 | |||
449 | // If we found something private we can end here |
||
450 | if ($attributeDefinition instanceof AttributeDefinition && |
||
451 | $attributeDefinition->getVisibility() === 'private' |
||
452 | ) { |
||
453 | // Set the private context to true and return it |
||
454 | $assertion->setPrivateContext(true); |
||
455 | |||
456 | return; |
||
457 | } |
||
458 | } |
||
459 | } |
||
460 | } |
||
461 | |||
462 | /** |
||
463 | * Will try to figure out if the passed assertion has a private context or not. |
||
464 | * This information will be entered into the assertion which will then be returned. |
||
465 | * |
||
466 | * @param \AppserverIo\Psr\MetaobjectProtocol\Dbc\Assertions\AssertionInterface $assertion The assertion we need the minimal scope for |
||
467 | * |
||
468 | * @return void |
||
469 | */ |
||
470 | protected function determineMinimalScope(AssertionInterface $assertion) |
||
471 | { |
||
472 | // Get the string to check for dynamic properties |
||
473 | $assertionString = $assertion->getString(); |
||
474 | |||
475 | // Do we have method calls? If so we have at least structure scope |
||
476 | $methodCalls = $this->filterMethodCalls($assertionString); |
||
477 | if (!empty($methodCalls)) { |
||
478 | $assertion->setMinScope('structure'); |
||
479 | } |
||
480 | |||
481 | // Do we have any attributes? If so we have at least structure scope |
||
482 | $attributes = $this->filterAttributes($assertionString); |
||
483 | if (!empty($attributes)) { |
||
484 | $assertion->setMinScope('structure'); |
||
485 | } |
||
486 | } |
||
487 | } |
||
488 |