/** * Analyzes the return-types of the current function and reports errors, if necessary * * @param PC_Engine_Scope $scope the current scope */ public function analyze($scope) { $funcname = $scope->get_name_of(T_FUNC_C); $classname = $scope->get_name_of(T_CLASS_C); $func = $this->env->get_types()->get_method_or_func($classname, $funcname); if ($func && !$func->is_abstract()) { $hasnull = false; $hasother = false; foreach ($this->allrettypes as $t) { if ($t === null) { $hasnull = true; } else { $hasother = true; } } if (count($this->allrettypes) == 0) { $mtype = PC_Obj_MultiType::create_void(); } else { $mtype = new PC_Obj_MultiType(); foreach ($this->allrettypes as $t) { if ($t !== null) { $mtype->merge($t, false); } else { $mtype->merge(PC_Obj_MultiType::create_void(), false); } } } if ($func->is_constructor()) { if ($hasother) { $this->report($func, 'The constructor of "' . $classname . '" has a return-statement with expression', PC_Obj_Error::E_S_CONSTR_RETURN); } } else { $name = ($classname ? '#' . $classname . '#::' : '') . $funcname; // empty return-expression and non-empty? if ($hasnull && $hasother) { $this->report($func, 'The function/method "' . $name . '" has return-' . 'statements without expression and return-statements with expression', PC_Obj_Error::E_S_MIXED_RET_AND_NO_RET); } $void = new PC_Obj_Type(PC_Obj_Type::VOID); $docreturn = $func->has_return_doc() && !$func->get_return_type()->contains($void); if ($docreturn && !$hasother) { $this->report($func, 'The function/method "' . $name . '" has a return-specification in PHPDoc' . ', but does not return a value', PC_Obj_Error::E_S_RET_SPEC_BUT_NO_RET); } else { if (!$docreturn && !$func->is_anonymous() && $hasother) { $this->report($func, 'The function/method "' . $name . '" has no return-specification in PHPDoc' . ', but does return a value', PC_Obj_Error::E_S_RET_BUT_NO_RET_SPEC); } else { if ($this->has_forbidden($this->allrettypes, $func->get_return_type())) { $this->report($func, 'The return-specification (PHPDoc) of function/method "' . $name . '" does not match with ' . 'the returned values (spec="' . $func->get_return_type() . '", returns="' . $mtype . '")', PC_Obj_Error::E_S_RETURNS_DIFFER_FROM_SPEC); } } } } if ($func->get_return_type()->is_unknown()) { $func->set_return_type($mtype); } } $this->allrettypes = array(); }
/** * Analyzes all throw statements that have been found in the current function and checks their * validity against the PHPDoc throw specifications. * * @param PC_Engine_Scope $scope the current scope */ public function analyze($scope) { $funcname = $scope->get_name_of(T_FUNC_C); $classname = $scope->get_name_of(T_CLASS_C); $func = $this->env->get_types()->get_method_or_func($classname, $funcname); if ($func && !$func->is_abstract()) { $name = ($classname ? '#' . $classname . '#::' : '') . $funcname; foreach ($func->get_throws() as $tclass => $ttype) { // if only the parent function specifies it, we are not forced to throw it if ($ttype == PC_Obj_Method::THROW_PARENT) { continue; } $found = false; foreach ($this->allthrows as list($origin, $mtype)) { foreach ($mtype->get_types() as $type) { if (strcasecmp($type->get_class(), $tclass) == 0) { $found = true; break; } } } if (!$found) { $this->report($func, 'The function/method "' . $name . '" throws "' . $tclass . '" according to PHPDoc' . ', but does not throw it', PC_Obj_Error::E_S_DOC_WITHOUT_THROW); } } foreach ($this->allthrows as list($origin, $mtype)) { // ignore missing throws specifications that are only thrown by called functions if ($origin == PC_Obj_Method::THROW_FUNC) { continue; } foreach ($mtype->get_types() as $type) { if ($type->get_type() == PC_Obj_Type::OBJECT) { if (!$func->contains_throw($type->get_class())) { $this->report($func, 'The function/method "' . $name . '" does not throw "' . $type . '" according to PHPDoc' . ', but throws it', PC_Obj_Error::E_S_THROW_NOT_IN_DOC); } } else { $this->report($func, 'The function/method "' . $name . '" throws a non-object (' . $type . ')', PC_Obj_Error::E_S_THROW_INVALID); } } } } $this->allthrows = array(); }
public function advance($parser) { if ($this->pos >= 0) { $type = $this->tokens[$this->pos][0]; switch ($type) { case T_FOR: case T_FOREACH: $this->start_loop(); break; case T_DO: $this->ignoreNextWhile = true; $this->start_loop(); break; case T_WHILE: if (!$this->ignoreNextWhile) { $this->start_loop(); } $this->ignoreNextWhile = false; break; case T_SWITCH: case T_TRY: $this->start_cond(); break; case T_IF: if (!$this->lastWasElse) { $this->start_cond(); } else { // count the number of "T_ELSE T_IF" because for each of those we get another call // to end_cond() $this->vars->set_elseif(); } break; case T_ELSEIF: $this->start_cond(true, false); break; case T_ELSE: $this->start_cond(true, true); break; } $this->lastWasElse = $type == T_ELSE; } $res = parent::advance($parser); if ($this->lastComment && $this->lastComment != $this->lastCheckComment) { // it was a comment, so lets see if it contains a "@var $<name> <type>" that gives us a // hint what type a variable has. $matches = array(); if (preg_match('/\\@var\\s+\\$([a-z0-9_]+)\\s+(\\S+)/i', $this->lastComment, $matches)) { // do we know that variable? $scopename = $this->scope->get_name(); if ($this->vars->exists($scopename, $matches[1])) { // ok, determine type and set it $type = PC_Obj_MultiType::get_type_by_name($matches[2]); $var = $this->vars->get($scopename, $matches[1]); $var->set_type($type); } } $this->lastCheckComment = $this->lastComment; } return $res; }
/** * Changes the variable with given name in the current scope * * @param string $file the current file * @param int $line the current line * @param PC_Engine_Scope $scope the current scope * @param array $layer the current layer * @param string $name the var-name * @param PC_Obj_Variable $backup the backup (0 if not present before the layer) * @param bool $present whether its present in all blocks in this layer */ private function change_var($file, $line, $scope, $layer, $name, $backup, $present) { $scopename = $scope->get_name(); // if its present in all blocks, merge the types if ($present) { // start with the type in scope; thats the one from the last block $mtype = $this->vars[$scopename][$name]->get_type(); // don't include the first block since thats the backup from the previous layer for ($i = 1; $i <= $layer['blockno']; $i++) { $mtype->merge($layer['vars'][$i][$name]->get_type()); } // note that this may discard the old value, if the variable was present $this->vars[$scopename][$name] = new PC_Obj_Variable($file, $line, $name, $mtype, $scope->get_name_of(T_FUNC_C), $scope->get_name_of(T_CLASS_C)); } else { if ($backup !== 0) { $mtype = $this->vars[$scopename][$name]->get_type(); for ($i = 0; $i <= $layer['blockno']; $i++) { if (isset($layer['vars'][$i][$name])) { $mtype->merge($layer['vars'][$i][$name]->get_type()); } } } else { if (!isset($this->vars[$scopename][$name])) { $this->vars[$scopename][$name] = new PC_Obj_Variable($file, $line, $name, new PC_Obj_MultiType(), $scope->get_name_of(T_FUNC_C), $scope->get_name_of(T_CLASS_C)); } else { $this->vars[$scopename][$name]->set_type(new PC_Obj_MultiType()); } } } // if there is a previous layer and the var is not known there in the last block, put // the first backup from this block in it. because this is the previous value for the previous // block, if it hasn't been assigned there if (count($this->layers) > 0) { $prevlayer =& $this->layers[count($this->layers) - 1]; if (!isset($prevlayer['vars'][$prevlayer['blockno']][$name])) { $prevlayer['vars'][$prevlayer['blockno']][$name] = $backup; } } }