protected function emitError($msg, Op $op) { echo $msg; echo " "; echo $op->getFile() . ":" . $op->getLine(); echo "\n"; }
private function dumpOp(Op $op) { $result = $op->getType(); foreach ($op->getVariableNames() as $varName) { $result .= "\n {$varName}: "; $result .= $this->indent($this->dumpOperand($op->{$varName})); } foreach ($op->getSubBlocks() as $subBlock) { $result .= "\n {$subBlock}: " . $this->indent($this->dumpBlockRef($op->{$subBlock})); } return $result; }
public function enterOp(Op $op, Block $block) { foreach ($op->getVariableNames() as $name) { $var = $op->{$name}; if (!is_array($var)) { $var = [$var]; } foreach ($var as $v) { if (is_null($v)) { continue; } $this->variables->attach($v); } } }
public function leaveOp(Op $op, Block $block) { if (!$op instanceof Op\Stmt\JumpIf) { return null; } if (!$op->cond instanceof Operand\Literal) { // Non-constant op return null; } // TODO: Figure out how to eliminate redundant Phi vars (eliminated phi vars) if ($op->cond->value) { return new Op\Stmt\Jump($op->if, $op->getAttributes()); } return new Op\Stmt\Jump($op->else, $op->getAttributes()); }
protected function renderOp(Op $op) { $result = $op->getType(); if ($op instanceof Op\CallableOp) { $result .= '<' . $op->name->value . '>'; foreach ($op->getParams() as $key => $param) { $result .= $this->indent("\nParam[{$key}]: " . $this->renderOperand($param->result)); } } if ($op instanceof Op\Expr\Assertion) { $result .= "<" . $this->renderAssertion($op->assertion) . ">"; } foreach ($op->getVariableNames() as $varName) { $vars = $op->{$varName}; if (!is_array($vars)) { $vars = [$vars]; } foreach ($vars as $var) { if (!$var) { continue; } $result .= "\n {$varName}: "; $result .= $this->indent($this->renderOperand($var)); } } $childBlocks = []; foreach ($op->getSubBlocks() as $blockName) { $sub = $op->{$blockName}; if (is_null($sub)) { continue; } if (!is_array($sub)) { $sub = [$sub]; } foreach ($sub as $subBlock) { if (!$subBlock) { continue; } $this->enqueueBlock($subBlock); $childBlocks[] = ["block" => $subBlock, "name" => $blockName]; } } return ["op" => $op, "label" => $result, "childBlocks" => $childBlocks]; }
protected function renderOp(Op $op) { $result = $op->getType(); if ($op instanceof Op\CallableOp) { $func = $op->getFunc(); $result .= "<" . $func->name . ">"; } if ($op instanceof Op\Expr\Assertion) { $result .= "<" . $this->renderAssertion($op->assertion) . ">"; } foreach ($op->getVariableNames() as $varName) { $vars = $op->{$varName}; if (is_array($vars)) { foreach ($vars as $key => $var) { if (!$var) { continue; } $result .= "\n {$varName}[{$key}]: "; $result .= $this->indent($this->renderOperand($var)); } } elseif ($vars) { $result .= "\n {$varName}: "; $result .= $this->indent($this->renderOperand($vars)); } } $childBlocks = []; foreach ($op->getSubBlocks() as $blockName) { $sub = $op->{$blockName}; if (is_array($sub)) { foreach ($sub as $key => $subBlock) { if (!$subBlock) { continue; } $this->enqueueBlock($subBlock); $childBlocks[] = ["block" => $subBlock, "name" => $blockName . "[" . $key . "]"]; } } elseif ($sub) { $this->enqueueBlock($sub); $childBlocks[] = ["block" => $sub, "name" => $blockName]; } } return ["op" => $op, "label" => $result, "childBlocks" => $childBlocks]; }
public static function removeUsage(Operand $var, Op $op) { foreach ($op->getVariableNames() as $varName) { $vars = $op->{$varName}; $newVars = []; if (!is_array($vars)) { $vars = [$vars]; } foreach ($vars as $key => $value) { if ($value !== $var) { $newVars[$key] = $value; } } if (!is_array($op->{$varName})) { $op->{$varName} = array_shift($newVars); } else { $op->{$varName} = array_keys($newVars); } } }
protected function resolveVarOp(Operand $var, Op $op, \SplObjectStorage $resolved) { switch ($op->getType()) { case 'Expr_Array': $types = []; foreach ($op->values as $value) { if (!isset($resolved[$value])) { return false; } $types[] = $resolved[$value]; } if (empty($types)) { return [new Type(Type::TYPE_ARRAY)]; } $r = $this->computeMergedType($types); if ($r) { return [new Type(Type::TYPE_ARRAY, [$r])]; } case 'Expr_Cast_Array': // Todo: determine subtypes better return [new Type(Type::TYPE_ARRAY)]; case 'Expr_ArrayDimFetch': if ($resolved->contains($op->var)) { // Todo: determine subtypes better $type = $resolved[$op->var]; if ($type->subTypes) { return $type->subTypes; } if ($type->type === Type::TYPE_STRING) { return [$type]; } return [Type::mixed()]; } break; case 'Expr_Assign': case 'Expr_AssignRef': if ($resolved->contains($op->expr)) { return [$resolved[$op->expr]]; } break; case 'Expr_InstanceOf': case 'Expr_BinaryOp_Equal': case 'Expr_BinaryOp_NotEqual': case 'Expr_BinaryOp_Greater': case 'Expr_BinaryOp_GreaterOrEqual': case 'Expr_BinaryOp_Identical': case 'Expr_BinaryOp_NotIdentical': case 'Expr_BinaryOp_Smaller': case 'Expr_BinaryOp_SmallerOrEqual': case 'Expr_BinaryOp_LogicalAnd': case 'Expr_BinaryOp_LogicalOr': case 'Expr_BinaryOp_LogicalXor': case 'Expr_BooleanNot': case 'Expr_Cast_Bool': case 'Expr_Empty': case 'Expr_Isset': return [Type::bool()]; case 'Expr_BinaryOp_BitwiseAnd': case 'Expr_BinaryOp_BitwiseOr': case 'Expr_BinaryOp_BitwiseXor': if ($resolved->contains($op->left) && $resolved->contains($op->right)) { switch ([$resolved[$op->left]->type, $resolved[$op->right]->type]) { case [Type::TYPE_STRING, Type::TYPE_STRING]: return [Type::string()]; default: return [Type::int()]; } } break; case 'Expr_BitwiseNot': if ($resolved->contains($op->expr)) { if ($resolved[$op->expr]->type === Type::TYPE_STRING) { return [Type::string()]; } return [Type::int()]; } break; case 'Expr_BinaryOp_Div': case 'Expr_BinaryOp_Plus': case 'Expr_BinaryOp_Minus': case 'Expr_BinaryOp_Mul': if ($resolved->contains($op->left) && $resolved->contains($op->right)) { switch ([$resolved[$op->left]->type, $resolved[$op->right]->type]) { case [Type::TYPE_LONG, Type::TYPE_LONG]: return [Type::int()]; case [Type::TYPE_DOUBLE, TYPE::TYPE_LONG]: case [Type::TYPE_LONG, TYPE::TYPE_DOUBLE]: case [Type::TYPE_DOUBLE, TYPE::TYPE_DOUBLE]: return [Type::float()]; case [Type::TYPE_ARRAY, Type::TYPE_ARRAY]: $sub = $this->computeMergedType(array_merge($resolved[$op->left]->subTypes, $resolved[$op->right]->subTypes)); if ($sub) { return [new Type(Type::TYPE_ARRAY, [$sub])]; } return [new Type(Type::TYPE_ARRAY)]; default: throw new \RuntimeException("Math op on unknown types {$resolved[$op->left]} + {$resolved[$op->right]}"); } } break; case 'Expr_BinaryOp_Concat': case 'Expr_Cast_String': case 'Expr_ConcatList': return [Type::string()]; case 'Expr_BinaryOp_Mod': case 'Expr_BinaryOp_ShiftLeft': case 'Expr_BinaryOp_ShiftRight': case 'Expr_Cast_Int': case 'Expr_Print': return [Type::int()]; case 'Expr_Cast_Double': return [Type::float()]; case 'Expr_Cast_Object': if ($resolved->contains($op->expr)) { if ($resolved[$op->expr]->type->resolves(Type::object())) { return [$resolved[$op->expr]]; } return [new Type(Type::TYPE_OBJECT, [], 'stdClass')]; } break; case 'Expr_Clone': if ($resolved->contains($op->expr)) { return [$resolved[$op->expr]]; } break; case 'Expr_Closure': return [new Type(Type::TYPE_OBJECT, [], "Closure")]; case 'Expr_FuncCall': if ($op->name instanceof Operand\Literal) { $name = strtolower($op->name->value); if (isset($this->components['functionLookup'][$name])) { $result = []; foreach ($this->components['functionLookup'][$name] as $func) { if ($func->returnType) { $result[] = Type::fromDecl($func->returnType->value); } else { // Check doc comment $result[] = Type::extractTypeFromComment("return", $func->getAttribute('doccomment')); } } return $result; } else { if (isset($this->components['internalTypeInfo']->functions[$name])) { $type = $this->components['internalTypeInfo']->functions[$name]; if (empty($type['return'])) { return false; } return [Type::fromDecl($type['return'])]; } } } // we can't resolve the function return false; case 'Expr_List': if ($op->result === $var) { return [new Type(Type::TYPE_ARRAY)]; } // TODO: infer this return false; case 'Expr_New': $type = $this->getClassType($op->class, $resolved); if ($type) { return [$type]; } return [Type::object()]; case 'Expr_Param': $docType = Type::extractTypeFromComment("param", $op->function->getAttribute('doccomment'), $op->name->value); if ($op->type) { $type = Type::fromDecl($op->type->value); if ($op->defaultVar) { if ($op->defaultBlock->children[0]->getType() === "Expr_ConstFetch" && strtolower($op->defaultBlock->children[0]->name->value) === "null") { $type = (new Type(Type::TYPE_UNION, [$type, Type::null()]))->simplify(); } } if ($docType !== Type::mixed() && $this->components['typeResolver']->resolves($docType, $type)) { // return the more specific return [$docType]; } return [$type]; } return [$docType]; case 'Expr_PropertyFetch': case 'Expr_StaticPropertyFetch': if (!$op->name instanceof Operand\Literal) { // variable property fetch return [Type::mixed()]; } $propName = $op->name->value; if ($op instanceof Op\Expr\StaticPropertyFetch) { $objType = $this->getClassType($op->class, $resolved); } else { $objType = $this->getClassType($op->var, $resolved); } if ($objType) { return $this->resolveProperty($objType, $propName); } return false; case 'Expr_Yield': case 'Expr_Include': // TODO: we may be able to determine these... return false; case 'Expr_Assertion': $tmp = $this->processAssertion($op->assertion, $op->expr, $resolved); if ($tmp) { return [$tmp]; } return false; case 'Expr_TypeUnAssert': throw new \RuntimeException("Unassertions should not occur anymore"); case 'Expr_UnaryMinus': case 'Expr_UnaryPlus': if ($resolved->contains($op->expr)) { switch ($resolved[$op->expr]->type) { case Type::TYPE_LONG: case Type::TYPE_DOUBLE: return [$resolved[$op->expr]]; } return [Type::numeric()]; } break; case 'Expr_Eval': return false; case 'Iterator_Key': if ($resolved->contains($op->var)) { // TODO: implement this as well return false; } break; case 'Expr_Exit': case 'Iterator_Reset': return [Type::null()]; case 'Iterator_Valid': return [Type::bool()]; case 'Iterator_Value': if ($resolved->contains($op->var)) { if ($resolved[$op->var]->subTypes) { return $resolved[$op->var]->subTypes; } return false; } break; case 'Expr_StaticCall': return $this->resolveMethodCall($op->class, $op->name, $op, $resolved); case 'Expr_MethodCall': return $this->resolveMethodCall($op->var, $op->name, $op, $resolved); case 'Expr_ConstFetch': if ($op->name instanceof Operand\Literal) { $constant = strtolower($op->name->value); switch ($constant) { case 'true': case 'false': return [Type::bool()]; case 'null': return [Type::null()]; default: if (isset($this->components['constants'][$op->name->value])) { $return = []; foreach ($this->components['constants'][$op->name->value] as $value) { if (!$resolved->contains($value->value)) { return false; } $return[] = $resolved[$value->value]; } return $return; } } } return false; case 'Expr_ClassConstFetch': //TODO $classes = []; if ($op->class instanceof Operand\Literal) { $class = strtolower($op->class->value); return $this->resolveClassConstant($class, $op, $resolved); } elseif ($resolved->contains($op->class)) { $type = $resolved[$op->class]; if ($type->type !== Type::TYPE_OBJECT || empty($type->userType)) { // give up return false; } return $this->resolveClassConstant(strtolower($type->userType), $op, $resolved); } return false; case 'Phi': $types = []; $resolveFully = true; foreach ($op->vars as $v) { if ($resolved->contains($v)) { $types[] = $resolved[$v]; } else { $resolveFully = false; } } if (empty($types)) { return false; } $type = $this->computeMergedType($types); if ($type) { if ($resolveFully) { return [$type]; } // leave on unresolved list to try again next round $resolved[$var] = $type; } return false; default: throw new \RuntimeException("Unknown operand prefix type: " . $op->getType()); } return false; }
public function __construct(array $attributes = []) { parent::__construct($attributes); }
public function leaveOp(Op $op, Block $block) { if (!$op instanceof Op\Expr\BinaryOp) { return null; } if (!$op->left instanceof Operand\Literal || !$op->right instanceof Operand\Literal) { // Non-constant op return null; } switch ($op->getType()) { case 'Expr_BinaryOp_BitwiseAnd': $newValue = new Operand\Literal($op->left->value & $op->right->value); break; case 'Expr_BinaryOp_BitwiseOr': $newValue = new Operand\Literal($op->left->value | $op->right->value); break; case 'Expr_BinaryOp_BitwiseXor': $newValue = new Operand\Literal($op->left->value ^ $op->right->value); break; case 'Expr_BinaryOp_Coalesce': if ($op->left->value === null) { throw new \RuntimeException("Not possible yet"); } $newValue = new Operand\Literal($op->left->value); break; case 'Expr_BinaryOp_Concat': $newValue = new Operand\Literal($op->left->value . $op->right->value); break; case 'Expr_BinaryOp_Div': $newValue = new Operand\Literal($op->left->value / $op->right->value); break; case 'Expr_BinaryOp_Equal': $newValue = new Operand\Literal($op->left->value == $op->right->value); break; case 'Expr_BinaryOp_Greater': $newValue = new Operand\Literal($op->left->value > $op->right->value); break; case 'Expr_BinaryOp_GreaterOrEqual': $newValue = new Operand\Literal($op->left->value >= $op->right->value); break; case 'Expr_BinaryOp_Identical': $newValue = new Operand\Literal($op->left->value === $op->right->value); break; case 'Expr_BinaryOp_LogicalXor': $newValue = new Operand\Literal($op->left->value xor $op->right->value); break; case 'Expr_BinaryOp_Minus': $newValue = new Operand\Literal($op->left->value - $op->right->value); break; case 'Expr_BinaryOp_Mod': $newValue = new Operand\Literal($op->left->value % $op->right->value); break; case 'Expr_BinaryOp_Mul': $newValue = new Operand\Literal($op->left->value * $op->right->value); break; case 'Expr_BinaryOp_NotEqual': $newValue = new Operand\Literal($op->left->value != $op->right->value); break; case 'Expr_BinaryOp_NotIdentical': $newValue = new Operand\Literal($op->left->value !== $op->right->value); break; case 'Expr_BinaryOp_Plus': $newValue = new Operand\Literal($op->left->value + $op->right->value); break; case 'Expr_BinaryOp_Pow': $newValue = new Operand\Literal(pow($op->left->value, $op->right->value)); break; case 'Expr_BinaryOp_ShiftLeft': $newValue = new Operand\Literal($op->left->value << $op->right->value); break; case 'Expr_BinaryOp_ShiftRight': $newValue = new Operand\Literal($op->left->value >> $op->right->value); break; case 'Expr_BinaryOp_Smaller': $newValue = new Operand\Literal($op->left->value < $op->right->value); break; case 'Expr_BinaryOp_SmallerOrEqual': $newValue = new Operand\Literal($op->left->value <= $op->right->value); break; case 'Expr_BinaryOp_Spaceship': $value = 0; if ($op->left->value < $op->right->value) { $value = -1; } elseif ($op->left->value > $op->right->value) { $value = 1; } $newValue = new Operand\Literal($value); break; default: throw new \RuntimeException("Unknown constant op found: " . $op->getType()); } $newValue->type = Type::fromValue($newValue->value); Helper::replaceVar($op->result, $newValue); return Visitor::REMOVE_OP; }
public function __construct(array $attributes = []) { parent::__construct($attributes); $this->result = $this->addWriteRef(new Temporary()); }
public function leaveOp(Op $op, Block $block) { echo "Leave Op " . $op->getType() . "\n"; }
public function __construct(array $attributes = array()) { parent::__construct($attributes); $this->result = new Variable(); }
protected function resolveVarOp(Operand $var, Op $op, SplObjectStorage $resolved) { $method = 'resolveOp_' . $op->getType(); if (method_exists($this, $method)) { return call_user_func([$this, $method], $var, $op, $resolved); } switch ($op->getType()) { case 'Expr_InstanceOf': case 'Expr_BinaryOp_Equal': case 'Expr_BinaryOp_NotEqual': case 'Expr_BinaryOp_Greater': case 'Expr_BinaryOp_GreaterOrEqual': case 'Expr_BinaryOp_Identical': case 'Expr_BinaryOp_NotIdentical': case 'Expr_BinaryOp_Smaller': case 'Expr_BinaryOp_SmallerOrEqual': case 'Expr_BinaryOp_LogicalAnd': case 'Expr_BinaryOp_LogicalOr': case 'Expr_BinaryOp_LogicalXor': case 'Expr_BooleanNot': case 'Expr_Cast_Bool': case 'Expr_Empty': case 'Expr_Isset': return [Type::bool()]; case 'Expr_BinaryOp_BitwiseAnd': case 'Expr_BinaryOp_BitwiseOr': case 'Expr_BinaryOp_BitwiseXor': if ($resolved->contains($op->left) && $resolved->contains($op->right)) { switch ([$resolved[$op->left]->type, $resolved[$op->right]->type]) { case [Type::TYPE_STRING, Type::TYPE_STRING]: return [Type::string()]; default: return [Type::int()]; } } return false; case 'Expr_BitwiseNot': if ($resolved->contains($op->expr)) { switch ($resolved[$op->expr]->type) { case Type::TYPE_STRING: return [Type::string()]; default: return [Type::int()]; } } return false; case 'Expr_BinaryOp_Div': case 'Expr_BinaryOp_Plus': case 'Expr_BinaryOp_Minus': case 'Expr_BinaryOp_Mul': if ($resolved->contains($op->left) && $resolved->contains($op->right)) { switch ([$resolved[$op->left]->type, $resolved[$op->right]->type]) { case [Type::TYPE_LONG, Type::TYPE_LONG]: return [Type::int()]; case [Type::TYPE_DOUBLE, TYPE::TYPE_LONG]: case [Type::TYPE_LONG, TYPE::TYPE_DOUBLE]: case [Type::TYPE_DOUBLE, TYPE::TYPE_DOUBLE]: return [Type::float()]; case [Type::TYPE_ARRAY, Type::TYPE_ARRAY]: $sub = $this->computeMergedType(array_merge($resolved[$op->left]->subTypes, $resolved[$op->right]->subTypes)); if ($sub) { return [new Type(Type::TYPE_ARRAY, [$sub])]; } return [new Type(Type::TYPE_ARRAY)]; default: return [Type::mixed()]; throw new \RuntimeException("Math op on unknown types {$resolved[$op->left]} + {$resolved[$op->right]}"); } } return false; case 'Expr_BinaryOp_Concat': case 'Expr_Cast_String': case 'Expr_ConcatList': return [Type::string()]; case 'Expr_BinaryOp_Mod': case 'Expr_BinaryOp_ShiftLeft': case 'Expr_BinaryOp_ShiftRight': case 'Expr_Cast_Int': case 'Expr_Print': return [Type::int()]; case 'Expr_Cast_Double': return [Type::float()]; case 'Expr_UnaryMinus': case 'Expr_UnaryPlus': if ($resolved->contains($op->expr)) { switch ($resolved[$op->expr]->type) { case Type::TYPE_LONG: case Type::TYPE_DOUBLE: return [$resolved[$op->expr]]; } return [Type::numeric()]; } return false; case 'Expr_Eval': return false; case 'Iterator_Key': if ($resolved->contains($op->var)) { // TODO: implement this as well return false; } return false; case 'Expr_Exit': case 'Iterator_Reset': return [Type::null()]; case 'Iterator_Valid': return [Type::bool()]; case 'Iterator_Value': if ($resolved->contains($op->var)) { if ($resolved[$op->var]->subTypes) { return $resolved[$op->var]->subTypes; } return false; } return false; case 'Expr_StaticCall': return $this->resolveMethodCall($op->class, $op->name, $op, $resolved); case 'Expr_MethodCall': return $this->resolveMethodCall($op->var, $op->name, $op, $resolved); case 'Expr_Yield': case 'Expr_Include': // TODO: we may be able to determine these... return false; } throw new \LogicException("Unknown variable op found: " . $op->getType()); }
protected function resolveVarOp(Operand $var, Op $op, \SplObjectStorage $resolved) { switch ($op->getType()) { case 'Expr_Array': $types = []; foreach ($op->values as $value) { if (!isset($resolved[$value])) { return false; } $types[] = $resolved[$value]; } $r = $this->computeMergedType($types); if ($r) { return [new Type(Type::TYPE_ARRAY, [$r])]; } case 'Expr_Cast_Array': // Todo: determine subtypes better return [new Type(Type::TYPE_ARRAY)]; case 'Expr_ArrayDimFetch': if ($resolved->contains($op->var)) { // Todo: determine subtypes better $type = $resolved[$op->var]; if ($type->subTypes) { return $type->subTypes; } if ($type->type === Type::TYPE_STRING) { return [$type]; } return [new Type(Type::TYPE_MIXED)]; } break; case 'Expr_Assign': case 'Expr_AssignRef': if ($resolved->contains($op->expr)) { return [$resolved[$op->expr]]; } break; case 'Expr_BinaryOp_Equal': case 'Expr_BinaryOp_NotEqual': case 'Expr_BinaryOp_Greater': case 'Expr_BinaryOp_GreaterOrEqual': case 'Expr_BinaryOp_Identical': case 'Expr_BinaryOp_NotIdentical': case 'Expr_BinaryOp_Smaller': case 'Expr_BinaryOp_SmallerOrEqual': case 'Expr_BinaryOp_LogicalAnd': case 'Expr_BinaryOp_LogicalOr': case 'Expr_BinaryOp_LogicalXor': case 'Expr_BooleanNot': case 'Expr_Cast_Bool': case 'Expr_Empty': case 'Expr_InstanceOf': case 'Expr_Isset': return [new Type(Type::TYPE_BOOLEAN)]; case 'Expr_BinaryOp_BitwiseAnd': case 'Expr_BinaryOp_BitwiseOr': case 'Expr_BinaryOp_BitwiseXor': if ($resolved->contains($op->left) && $resolved->contains($op->right)) { switch ([$resolved[$op->left]->type, $resolved[$op->right]->type]) { case [Type::TYPE_STRING, Type::TYPE_STRING]: return [new Type(Type::TYPE_STRING)]; default: return [new Type(Type::TYPE_LONG)]; } } break; case 'Expr_BitwiseNot': if ($resolved->contains($op->expr)) { if ($resolved[$op->expr]->type === Type::TYPE_STRING) { return [new Type(Type::TYPE_STRING)]; } return [new Type(Type::TYPE_LONG)]; } break; case 'Expr_BinaryOp_Div': case 'Expr_BinaryOp_Plus': case 'Expr_BinaryOp_Minus': case 'Expr_BinaryOp_Mul': if ($resolved->contains($op->left) && $resolved->contains($op->right)) { switch ([$resolved[$op->left]->type, $resolved[$op->right]->type]) { case [Type::TYPE_LONG, Type::TYPE_LONG]: return [new Type(Type::TYPE_LONG)]; case [Type::TYPE_DOUBLE, TYPE::TYPE_LONG]: case [Type::TYPE_LONG, TYPE::TYPE_DOUBLE]: case [Type::TYPE_DOUBLE, TYPE::TYPE_DOUBLE]: case [Type::TYPE_MIXED, TYPE::TYPE_DOUBLE]: case [Type::TYPE_DOUBLE, TYPE::TYPE_MIXED]: case [Type::TYPE_NUMERIC, TYPE::TYPE_DOUBLE]: case [Type::TYPE_DOUBLE, TYPE::TYPE_NUMERIC]: return [new Type(Type::TYPE_DOUBLE)]; case [Type::TYPE_MIXED, Type::TYPE_MIXED]: case [Type::TYPE_MIXED, Type::TYPE_LONG]: case [Type::TYPE_LONG, Type::TYPE_MIXED]: case [Type::TYPE_NUMERIC, Type::TYPE_LONG]: case [Type::TYPE_LONG, Type::TYPE_NUMERIC]: case [Type::TYPE_NUMERIC, Type::TYPE_MIXED]: case [Type::TYPE_MIXED, Type::TYPE_NUMERIC]: case [Type::TYPE_NUMERIC, Type::TYPE_NUMERIC]: return [new Type(Type::TYPE_NUMERIC)]; case [Type::TYPE_ARRAY, Type::TYPE_ARRAY]: $sub = $this->computeMergedType(array_merge($resolved[$op->left]->subTypes), $resolved[$op->right]->subTypes); if ($sub) { return [new Type(Type::TYPE_ARRAY, [$sub])]; } return [new Type(Type::TYPE_ARRAY)]; default: return [new Type(Type::TYPE_MIXED)]; } } break; case 'Expr_BinaryOp_Concat': case 'Expr_Cast_String': case 'Expr_ConcatList': return [new Type(Type::TYPE_STRING)]; case 'Expr_BinaryOp_Mod': case 'Expr_BinaryOp_ShiftLeft': case 'Expr_BinaryOp_ShiftRight': case 'Expr_Cast_Int': case 'Expr_Print': return [new Type(Type::TYPE_LONG)]; case 'Expr_Cast_Double': return [new Type(Type::TYPE_DOUBLE)]; case 'Expr_Cast_Object': if ($resolved->contains($op->expr)) { if ($resolved[$op->expr]->type === Type::TYPE_USER) { return [$resolved[$op->expr]]; } return [new Type(Type::TYPE_USER, null, 'stdClass')]; } break; case 'Expr_Clone': if ($resolved->contains($op->expr)) { return [$resolved[$op->expr]]; } break; case 'Expr_Closure': return [new Type(Type::TYPE_USER, [], ["Closure"])]; case 'Expr_FuncCall': if ($op->name instanceof Operand\Literal) { $name = strtolower($op->name->value); if (isset($this->components['functionLookup'][$name])) { $result = []; foreach ($this->components['functionLookup'][$name] as $func) { if ($func->returnType) { $result[] = Type::fromDecl($func->returnType->value); } else { // Check doc comment $result[] = Type::extractTypeFromComment("return", $func->getAttribute('doccomment')); } } return $result; } else { if (isset($this->components['internalTypeInfo']->functions[$name])) { $type = $this->components['internalTypeInfo']->functions[$name]; if (empty($type['return'])) { return [new Type(Type::TYPE_MIXED)]; } return [Type::fromDecl($type['return'])]; } } } // we can't resolve the function return [new Type(Type::TYPE_MIXED)]; break; case 'Expr_List': if ($op->result === $var) { return [new Type(Type::TYPE_ARRAY)]; } // TODO: infer this return [new Type(Type::TYPE_MIXED)]; case 'Expr_New': if ($op->class instanceof Operand\Literal) { return [new Type(Type::TYPE_USER, [], [$op->class->value])]; } return [new Type(Type::TYPE_OBJECT)]; case 'Expr_Param': if ($op->type) { $type = Type::fromDecl($op->type->value); if ($op->defaultVar) { if ($op->defaultBlock->children[0]->getType() === "Expr_ConstFetch" && strtolower($op->defaultBlock->children[0]->name->value) === "null") { $type->type |= Type::TYPE_NULL; } } return [$type]; } return [Type::extractTypeFromComment("param", $op->function->getAttribute('doccomment'), $op->name->value)]; case 'Expr_Yield': case 'Expr_Include': case 'Expr_PropertyFetch': case 'Expr_StaticPropertyFetch': case 'Stmt_Property': // TODO: we may be able to determine these... return [new Type(Type::TYPE_MIXED)]; case 'Expr_TypeAssert': return [Type::fromDecl($op->assertedType)]; case 'Expr_TypeUnAssert': if ($resolved->contains($op->assert->expr)) { return [$resolved[$op->assert->expr]]; } case 'Expr_UnaryMinus': case 'Expr_UnaryPlus': if ($resolved->contains($op->expr)) { switch ($resolved[$op->expr]->type) { case Type::TYPE_LONG: case Type::TYPE_DOUBLE: return [$resolved[$op->expr]]; } return [new Type(Type::TYPE_NUMERIC)]; } break; case 'Expr_Eval': return [new Type(Type::TYPE_MIXED)]; case 'Iterator_Key': if ($resolved->contains($op->var)) { // TODO: implement this as well return [new Type(Type::TYPE_MIXED)]; } break; case 'Expr_Exit': case 'Iterator_Reset': return [new Type(Type::TYPE_VOID)]; case 'Iterator_Valid': return [new Type(Type::TYPE_BOOLEAN)]; case 'Iterator_Value': if ($resolved->contains($op->var)) { if ($resolved[$op->var]->subTypes) { return $resolved[$op->var]->subTypes; } return [new Type(Type::TYPE_MIXED)]; } break; case 'Expr_MethodCall': if (!$op->name instanceof Operand\Literal) { // variable method call return [new Type(Type::TYPE_MIXED)]; } if ($resolved->contains($op->var)) { if ($resolved[$op->var]->type !== Type::TYPE_USER) { return [new Type(Type::TYPE_MIXED)]; } $types = []; foreach ($resolved[$op->var]->userTypes as $ut) { $className = strtolower($ut); if (!isset($this->components['resolves'][$className])) { return [new Type(Type::TYPE_MIXED)]; } foreach ($this->components['resolves'][$className] as $class) { $method = $this->findMethod($class, $op->name->value); if (!$method) { continue; } if (!$method->returnType) { $types[] = Type::extractTypeFromComment("return", $method->getAttribute('doccomment')); } else { $types[] = Type::fromDecl($method->returnType->value); } } } return $types; } break; case 'Expr_ConstFetch': if ($op->name instanceof Operand\Literal) { $constant = strtolower($op->name->value); switch ($constant) { case 'true': case 'false': return [new Type(Type::TYPE_BOOLEAN)]; case 'null': return [new Type(Type::TYPE_NULL)]; default: if (isset($this->components['constants'][$op->name->value])) { $return = []; foreach ($this->components['constants'][$op->name->value] as $value) { if (!$resolved->contains($value->value)) { return false; } $return[] = $resolved[$value->value]; } return $return; } } } return [new Type(Type::TYPE_MIXED)]; case 'Expr_StaticCall': return [new Type(Type::TYPE_MIXED)]; case 'Expr_ClassConstFetch': //TODO $classes = []; if ($op->class instanceof Operand\Literal) { $class = strtolower($op->class->value); return $this->resolveClassConstant($class, $op, $resolved); } elseif ($resolved->contains($op->class)) { $type = $resolved[$op->class]; if ($type->type !== Type::TYPE_USER) { // give up return [new Type(Type::TYPE_MIXED)]; } $types = []; foreach ($type->userTypes as $type) { $try = $this->resolveClassConstant(strtolower($type), $op, $resolved); if ($try) { $types = array_merge($types, $try); } else { return false; } } if ($types) { return $types; } return [new Type(Type::TYPE_MIXED)]; } return false; case 'Phi': $types = []; foreach ($op->vars as $var) { if ($resolved->contains($var)) { $types[] = $resolved[$var]; } else { // entire phi isn't resolved yet, can't process continue 2; } } if (empty($types)) { return false; } $type = $this->computeMergedType($types); if ($type) { return [$type]; } return false; default: throw new \RuntimeException("Unknown operand prefix type: " . $op->getType()); } return false; }
public function __construct(Operand $result, array $attributes = []) { parent::__construct($attributes); $this->result = $this->addWriteRef($result); }
private function replaceOpVariable(Operand $from, Operand $to, Op $op) { foreach ($op->getVariableNames() as $name) { if (is_null($op->{$name})) { continue; } if (is_array($op->{$name})) { // SIGH, PHP won't let me do this directly (parses as $op->($name[$key])) $result = $op->{$name}; $new = []; foreach ($result as $key => $value) { if ($value === $from) { $new[$key] = $to; if ($op->isWriteVariable($name)) { $to->addWriteOp($op); } else { $to->addUsage($op); } } else { $new[$key] = $value; } } $op->{$name} = $new; } elseif ($op->{$name} === $from) { $op->{$name} = $to; if ($op->isWriteVariable($name)) { $to->addWriteOp($op); } else { $to->addUsage($op); } } } }