Exemple #1
0
 /**
  * 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();
 }
Exemple #2
0
 /**
  * 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;
         }
     }
 }