/** * Adds a function-call * * @param PC_Obj_MultiType $class the class-name (or null) * @param PC_Obj_MultiType $func the function-name * @param array $args the function-arguments * @param bool $static whether its a static call * @return PC_Obj_MultiType the result */ public function add_call($class, $func, $args, $static = false) { if ($class !== null && !$class instanceof PC_Obj_MultiType) { return $this->handle_error('$class is invalid'); } if (!$func instanceof PC_Obj_MultiType) { return $this->handle_error('$func is invalid'); } if (!is_array($args)) { return $this->handle_error('$args is invalid'); } // if we don't know the function- or class-name, we can't do anything here $cname = $class !== null ? $class->get_string() : null; $fname = $func->get_string(); if ($fname === null || $class !== null && $cname === null) { return $this->create_unknown(); } // create call $call = new PC_Obj_Call($this->get_file(), $this->get_line()); // determine class- and function-name $call->set_function($fname); // do it manually here because we might not know the method $call->set_object_creation(strcasecmp($fname, '__construct') == 0 || strcasecmp($fname, $cname) == 0); if ($class !== null) { // support for php4 constructors if (strcasecmp($cname, $fname) == 0) { $call->set_object_creation(true); } else { if (strcasecmp($cname, 'parent') == 0) { // in this case its no object-creation for us because it would lead to reports like // instantiation of abstract classes when calling the constructor of an abstract // parent-class $call->set_object_creation(false); $cname = $this->scope->get_name_of(T_CLASS_C); $classobj = $this->env->get_types()->get_class($cname); if ($classobj === null || $classobj->get_super_class() == '') { return $this->create_unknown(); } $cname = $classobj->get_super_class(); // if we're in a static method, the call is always static $curfname = $this->scope->get_name_of(T_FUNC_C); $curfunc = $classobj->get_method($curfname); if ($curfunc === null || $curfunc->is_static()) { $static = true; } else { // if we're in a non-static method, its a static-call if the method we're calling is // static, and not if its not. i.e. we basically don't detect errors here $func = null; $super = $this->env->get_types()->get_class($cname); if ($super !== null) { $func = $super->get_method($fname); } $static = $func !== null && $func->is_static(); } } else { if (strcasecmp($cname, 'self') == 0) { $cname = $this->scope->get_name_of(T_CLASS_C); // self is static if its not a constructor-call $static = !$call->is_object_creation(); } } } $call->set_class($cname); } // clone because it might be a variable foreach ($args as $arg) { if (!$arg instanceof PC_Obj_MultiType) { $this->handle_error('$arg is invalid'); continue; } $call->add_argument(clone $arg); } $call->set_static($static); $this->env->get_types()->add_call($call); $funcobj = $this->env->get_types()->get_method_or_func($cname, $fname); $this->calls_analyzer->analyze($call); $this->modifiers_analyzer->analyze($this->scope, $call, $funcobj); if ($funcobj === null) { // if it's a constructor, we still know the type if ($call->is_object_creation()) { return PC_Obj_MultiType::create_object($cname); } return $this->create_unknown(); } // reference parameters implicitly create variables $i = 0; foreach ($funcobj->get_params() as $param) { if ($i >= count($args)) { break; } // this is a hack: we store the variable name for missing variable names when the variable // appears. when detecting calls, we check whether there are reference parameters and then // use the name to implicitly create the variable if ($param->is_reference() && $args[$i]->get_missing_varname() !== null) { $var = new PC_Obj_Variable($this->get_file(), $this->get_line(), $args[$i]->get_missing_varname(), clone $param->get_mtype()); $this->vars->set($this->scope->get_name(), $var); $args[$i]->set_missing_varname(null); } $i++; } $this->req_analyzer->analyze($call, $funcobj->get_version()->get_min(), $funcobj->get_version()->get_max()); // add the throws of the method to our throws foreach (array_keys($funcobj->get_throws()) as $tclass) { $this->throws_analyzer->add(PC_Obj_Method::THROW_FUNC, PC_Obj_MultiType::create_object($tclass)); } // if its a constructor we know the type directly if ($call->is_object_creation()) { return PC_Obj_MultiType::create_object($cname); } return clone $funcobj->get_return_type(); }
/** * Builds an instance of PC_Obj_Call from the given row * * @param array $row the row from the db * @return PC_Obj_Call the call * @throws Exception if the arguments can't be serialized */ private function build_call($row) { $c = new PC_Obj_Call($row['file'], $row['line']); $c->set_id($row['id']); $c->set_class($row['class']); $c->set_function($row['function']); $c->set_static($row['static']); $c->set_object_creation($row['objcreation']); $args = $row['arguments'] ? unserialize($row['arguments']) : array(); if ($args === false) { throw new Exception("Unable to unserialize '" . $row['arguments'] . "'"); } foreach ($args as $arg) { $c->add_argument($arg); } return $c; }