public function checkFileSlice(XRef_IProjectDatabase $db, $file_name, $file_slice) { foreach ($file_slice as $c) { list($class_name, $line_number) = $c; $lr = $db->lookupMethod($class_name, '__construct', $parent_only = true); // if the base class has constructor, and the constructor is not abstract // (it's valid to declare constructor in PHP's interfaces) if ($lr->code == XRef_LookupResult::FOUND && !is_null($lr->elements[0]->bodyStarts)) { // oops, child doesn't call parent's constructor $base_class_name = $lr->elements[0]->className; // check that the method itself doesn't call call_user_func() // if it does, that it can indirectly call the base class constructor // See Swiftmailer for examples $lr = $db->lookupMethod($class_name, '__construct'); if ($lr && $lr->code == XRef_LookupResult::FOUND && ($lr->elements[0]->flags & XRef_ProjectDatabase::FLAG_CALLS_USER_FUNC) != 0) { // notice - can't tell if the parent constructor is called or not $error_descr = array('code' => self::E_CANT_TELL_IF_PARENT_CALLED, 'text' => $class_name, 'params' => array($class_name), 'location' => array($file_name, $line_number)); } else { // warning $error_descr = array('code' => self::E_MISSED_CALL_TO_PARENT_CONSTRUCTOR, 'text' => $class_name, 'params' => array($class_name, $base_class_name), 'location' => array($file_name, $line_number)); } $this->errors[$file_name][] = $error_descr; } } }
public function checkFileSlice(XRef_IProjectDatabase $db, $file_name, $file_slice) { $checked_for_functions = $file_slice["checked"]; foreach ($file_slice["called"] as $s) { list($kind, $function_name, $extra, $line_number, $num_of_arguments) = $s; if ($kind == self::F_CLASS_METHOD) { $class_name = $extra; $lr = $db->lookupMethod($class_name, $function_name); if ($lr->code != XRef_LookupResult::FOUND) { // don't report error here, CheckClassAccess will do // missed methods and missed classes/base classes are covered by CheckClassAccess plugin continue; } } elseif ($kind == self::F_CLASS_CONSTRUCTOR) { $class_name = $extra; // try to find php5 constructor $lr = $db->lookupMethod($class_name, '__construct'); if ($lr->code != XRef_LookupResult::FOUND) { // or try to find php4 constructor $lr = $db->lookupMethod($class_name, $class_name); } if ($lr->code == XRef_LookupResult::FOUND) { // ok, found, check it later } elseif ($lr->code == XRef_LookupResult::CLASS_MISSING) { // hm, can't validate because class or base class is missing continue; } else { // no constructor found // default constructor with 0 arguments will be called if ($num_of_arguments != 0) { $this->errors[$file_name][] = array('code' => self::E_DEFAULT_CONSTRUCTOR, 'text' => $class_name, 'params' => array($class_name), 'location' => array($file_name, $line_number)); } continue; } } elseif ($kind == self::F_NOT_QUALIFIED_FUNC) { // that's tricky - // foo() can be // 1. current \namespace\foo() // 2. global foo() // 3. method of current class (self::foo()) without class prefix, error $lr = null; $namespace = $extra; if ($namespace) { // 1. try to find \namespace\foo() $lr = $db->lookupFunction("{$namespace}\\{$function_name}"); if ($lr->code == XRef_LookupResult::FOUND) { $function_name = "{$namespace}\\{$function_name}"; } } if (!$lr || $lr->code != XRef_LookupResult::FOUND) { // 2. try to find global foo() $lr = $db->lookupFunction($function_name); } if ($lr->code != XRef_LookupResult::FOUND) { // 3. try to find CurrentClass::foo() $from_class = $s[5]; if ($from_class) { $lr = $db->lookupMethod($from_class, $function_name); if ($lr->code == XRef_LookupResult::FOUND) { // oops, we did find it // looks like error $this->errors[$file_name][] = array('code' => self::E_UNQUALIFIED_METHOD, 'text' => $function_name, 'params' => array($function_name, $from_class), 'location' => array($file_name, $line_number)); continue; } } } if ($lr->code != XRef_LookupResult::FOUND) { if (!isset($checked_for_functions[$function_name])) { $this->errors[$file_name][] = array('code' => self::E_UNKNOWN_FUNCTION, 'text' => $function_name, 'params' => array($function_name), 'location' => array($file_name, $line_number)); } continue; } } elseif ($kind == self::F_FULLY_QUALIFIED_FUNC) { $lr = $db->lookupFunction($function_name); if ($lr->code != XRef_LookupResult::FOUND) { if (!isset($checked_for_functions[$function_name])) { $this->errors[$file_name][] = array('code' => self::E_UNKNOWN_FUNCTION, 'text' => $function_name, 'params' => array($function_name), 'location' => array($file_name, $line_number)); } continue; } } else { throw new Exception($kind); } // here check the number of arguments of found method/function if ($lr->code != XRef_LookupResult::FOUND) { throw new Exception($lr->code); } $f = $lr->elements[0]; // if the number of arguments doesn't match the number of parameters, // (and the function called doesn't call func_get_args() if ($num_of_arguments != count($f->parameters) && ($f->flags & XRef_ProjectDatabase::FLAG_CALLS_GET_ARGS) == 0) { $min_number_of_arguments = 0; foreach ($f->parameters as $p) { if ($p->hasDefaultValue || $p->name == '...') { break; } $min_number_of_arguments++; } $last_parameter = end($f->parameters); $max_number_of_arguments = $last_parameter && $last_parameter->name == '...' ? self::ELLIPSES_ARGS : count($f->parameters); if ($num_of_arguments < $min_number_of_arguments || $num_of_arguments > $max_number_of_arguments) { if ($min_number_of_arguments == $max_number_of_arguments) { $arg_str = $min_number_of_arguments; } elseif ($max_number_of_arguments == self::ELLIPSES_ARGS) { $arg_str = $min_number_of_arguments . "..n"; } else { $arg_str = $min_number_of_arguments . ".." . $max_number_of_arguments; } if ($kind == self::F_CLASS_CONSTRUCTOR) { $this->errors[$file_name][] = array('code' => self::E_WRONG_CONSTRUCTOR_ARGS, 'text' => $class_name, 'params' => array($class_name, $num_of_arguments, $arg_str), 'location' => array($file_name, $line_number)); } else { $this->errors[$file_name][] = array('code' => self::E_WRONG_NUMBER_OF_ARGS, 'text' => $function_name, 'params' => array($function_name, $num_of_arguments, $arg_str), 'location' => array($file_name, $line_number)); } } } } }
private function checkAccessError(XRef_IProjectDatabase $db, $class_name, $key, $name, $from_class, $is_static, $check_parent_only) { /** @var $lr XRef_LookupResult */ $lr = null; switch ($key) { case 'property': $lr = $db->lookupProperty($class_name, $name, $check_parent_only); break; case 'method': $lr = $db->lookupMethod($class_name, $name, $check_parent_only); break; case 'constant': $lr = $db->lookupConstant($class_name, $name, $check_parent_only); break; } if (!$lr || $lr->code == XRef_LookupResult::NOT_FOUND) { // definition not found if ($key == 'property') { $lr_magic = $db->lookupMethod($class_name, '__get', $check_parent_only); if ($lr_magic->code == XRef_LookupResult::FOUND) { // can't validate reference to (missing) property, // because it or it's base class has method '__get' return array(self::E_MAGIC_GETTER, $class_name, array($class_name)); } } if ($key == 'method' && $name == '__construct') { // ok, php creates a default constructor } else { switch ($key) { case 'method': $error_code = self::E_ACCESS_TO_UNDEFINED_METHOD; break; case 'constant': $error_code = self::E_ACCESS_TO_UNDEFINED_CONSTANT; break; case 'property': $error_code = self::E_ACCESS_TO_UNDEFINED_PROPERTY; break; default: throw new Exception($key); } $uniq = "{$from_class}/{$class_name}/{$key}/{$name}"; return array($error_code, $uniq, array($name, $class_name)); } } elseif ($lr->code == XRef_LookupResult::CLASS_MISSING) { // definition not found because definition of either class or its base class is missing $missing_class_name = $lr->missingClassName; if (!isset($this->ignore_missing_classes[strtolower($missing_class_name)])) { if (strtolower($missing_class_name) == strtolower($class_name)) { // class definition is missing return array(self::E_MISSING_CLASS, $class_name, array($class_name)); } else { // base class definition is missing return array(self::E_MISSING_BASE_CLASS, $class_name, array($class_name, $missing_class_name)); } } } else { // got definition, check access $attributes = $lr->elements[0]->attributes; $found_in_class = $lr->elements[0]->className; // 1. static vs. instance if ($key != 'constant' && !($key == 'method' && $name == '__construct')) { if ($is_static) { if (!XRef::isStatic($attributes)) { // reference to instance method or property as if they were static if ($key == 'method' || $key == 'property') { return array(self::E_ACCESS_INSTANCE_AS_STATIC, "{$from_class}/{$class_name}/{$key}/{$name}", array($name, $found_in_class)); } else { throw new Exception($key); } } } else { if ($key == 'property' && XRef::isStatic($attributes)) { // reference to static property as if it were instance return array(self::E_ACCESS_STATIC_AS_INSTANCE, "{$from_class}/{$class_name}/{$name}", array($name, $found_in_class)); } } } // 2. public, private, protected if (XRef::isPublic($attributes)) { // ok } elseif (XRef::isPrivate($attributes)) { if (!$from_class || strtolower($found_in_class) != strtolower($from_class)) { // attempt to access a private member (method or property) of class $found_in_class // from $class_name return array(self::E_PRIVATE_MEMBER, "{$from_class}/{$class_name}/{$name}", array($name, $found_in_class)); } } elseif (XRef::isProtected($attributes)) { if (!$from_class || !$this->isSubclassOf($from_class, $found_in_class)) { return array(self::E_PROTECTED_MEMBER, "{$from_class}/{$class_name}/{$name}", array($name, $found_in_class)); } } else { // shouldn't be here throw new Exception("Should be public? {$attributes}"); } // 3. check that the called method is defined, not only declared. // however, allow to call declared methods from abstract classes and traits if ($key == 'method' && is_null($lr->elements[0]->bodyStarts)) { $lc = $db->lookupClass($from_class); if ($lc && $lc->code == XRef_LookupResult::FOUND && ($lc->elements[0]->isAbstract || $lc->elements[0]->kind == T_TRAIT)) { // ok, allow to call abstract method } else { $found_in_class = $lr->elements[0]->className; return array(self::E_ACCESS_TO_UNDEFINED_METHOD, "{$from_class}/{$found_in_class}/{$name}", array($name, $found_in_class)); } } } return; }