function node_type($file, $namespace, $node, $current_scope, $current_class, &$taint = null, $check_var_exists = true) { global $classes, $functions, $scope, $namespace_map, $internal_arginfo; if (!$node instanceof \ast\Node) { if ($node === null) { return ''; } return type_map(gettype($node)); } else { if ($node->kind == \ast\AST_ARRAY) { return 'array'; } else { if ($node->kind == \ast\AST_BINARY_OP) { $taint = var_taint_check($file, $node, $current_scope); switch ($node->flags) { // Always a string from a concat case \ast\flags\BINARY_CONCAT: $temp_taint = false; node_type($file, $namespace, $node->children[0], $current_scope, $current_class, $temp_taint); if ($temp_taint) { $taint = true; return 'string'; } node_type($file, $namespace, $node->children[1], $current_scope, $current_class, $temp_taint); if ($temp_taint) { $taint = true; } return 'string'; break; // Boolean unless invalid operands // Boolean unless invalid operands case \ast\flags\BINARY_IS_IDENTICAL: case \ast\flags\BINARY_IS_NOT_IDENTICAL: case \ast\flags\BINARY_IS_EQUAL: case \ast\flags\BINARY_IS_NOT_EQUAL: case \ast\flags\BINARY_IS_SMALLER: case \ast\flags\BINARY_IS_SMALLER_OR_EQUAL: $taint = false; return 'bool'; break; // Add is special because you can add arrays // Add is special because you can add arrays case \ast\flags\BINARY_ADD: $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class); if (!$temp) { $left = ''; } else { $left = type_map($temp); } $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class); if (!$temp) { $right = ''; } else { $right = type_map($temp); } if ($left == 'array' && $right == 'array') { return 'array'; } else { if ($left == 'array' && !type_check($right, 'array')) { Log::err(Log::ETYPE, "invalid operator: left operand is array and right is not", $file, $node->lineno); return ''; } else { if ($right == 'array' && !type_check($left, 'array')) { Log::err(Log::ETYPE, "invalid operator: right operand is array and left is not", $file, $node->lineno); return ''; } else { if ($left == 'int' && $right == 'int') { return 'int'; } else { if ($left == 'float' || $right == 'float') { return 'float'; } else { if ($left == 'array' || $right == 'array') { // If it is a '+' and we know one side is an array and the other is unknown, assume array return 'array'; } } } } } } return 'int|float'; $taint = false; break; // Everything else should be an int/float // Everything else should be an int/float default: $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class); if (!$temp) { $left = ''; } else { $left = type_map($temp); } $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class); if (!$temp) { $right = ''; } else { $right = type_map($temp); } if ($left == 'array' || $right == 'array') { Log::err(Log::ETYPE, "invalid array operator", $file, $node->lineno); return ''; } else { if ($left == 'int' && $right == 'int') { return 'int'; } else { if ($left == 'float' || $right == 'float') { return 'float'; } } } return 'int|float'; $taint = false; break; } } else { if ($node->kind == \ast\AST_CAST) { $taint = var_taint_check($file, $node->children[0], $current_scope); switch ($node->flags) { case \ast\flags\TYPE_NULL: return 'null'; break; case \ast\flags\TYPE_BOOL: $taint = false; return 'bool'; break; case \ast\flags\TYPE_LONG: $taint = false; return 'int'; break; case \ast\flags\TYPE_DOUBLE: $taint = false; return 'float'; break; case \ast\flags\TYPE_STRING: return 'string'; break; case \ast\flags\TYPE_ARRAY: return 'array'; break; case \ast\flags\TYPE_OBJECT: return 'stdClass'; break; default: Log::err(Log::EFATAL, "Unknown type (" . $node->flags . ") in cast"); } } else { if ($node->kind == \ast\AST_NEW) { $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); if ($class_name) { return $classes[strtolower($class_name)]['type']; } return 'object'; } else { if ($node->kind == \ast\AST_DIM) { $taint = var_taint_check($file, $node->children[0], $current_scope); // TODO: Do something smart with array elements return ''; } else { if ($node->kind == \ast\AST_VAR) { return var_type($file, $node, $current_scope, $taint, $check_var_exists); } else { if ($node->kind == \ast\AST_ENCAPS_LIST) { foreach ($node->children as $encap) { if ($encap instanceof \ast\Node) { if (var_taint_check($file, $encap, $current_scope)) { $taint = true; } } } return "string"; } else { if ($node->kind == \ast\AST_CONST) { if ($node->children[0]->kind == \ast\AST_NAME) { if (defined($node->children[0]->children[0])) { return type_map(gettype(constant($node->children[0]->children[0]))); } else { // Todo: user-defined constant } } } else { if ($node->kind == \ast\AST_PROP) { if ($node->children[0]->kind == \ast\AST_VAR) { $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); if ($class_name && !$node->children[1] instanceof \ast\Node) { if (empty($classes[strtolower($class_name)]['properties'][$node->children[1]])) { return ''; } return $classes[strtolower($class_name)]['properties'][$node->children[1]]['value']; } } } else { if ($node->kind == \ast\AST_CALL) { if ($node->children[0]->kind == \ast\AST_NAME) { $func_name = $node->children[0]->children[0]; if ($node->children[0]->flags & \ast\flags\NAME_NOT_FQ) { $func = $namespace_map[T_FUNCTION][$file][strtolower($namespace . $func_name)] ?? $namespace_map[T_FUNCTION][$file][strtolower($func_name)] ?? $functions[strtolower($namespace . $func_name)] ?? $functions[strtolower($func_name)] ?? null; } else { $func = $functions[strtolower($func_name)] ?? null; } if ($func['file'] == 'internal' && empty($func['ret'])) { if (!empty($internal_arginfo[$func_name])) { return $internal_arginfo[$func_name][0] ?? ''; } } else { return $func['ret'] ?? ''; } } else { // TODO: Handle $func() and other cases that get here } } else { if ($node->kind == \ast\AST_STATIC_CALL) { $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); $method = find_method($class_name, $node->children[1]); if ($method) { return $method['ret'] ?? ''; } } else { if ($node->kind == \ast\AST_METHOD_CALL) { $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); if ($class_name) { $method_name = $node->children[1]; $method = find_method($class_name, $method_name); if ($method === false) { Log::err(Log::EUNDEF, "call to undeclared method {$class_name}->{$method_name}()", $file, $node->lineno); } else { if ($method != 'dynamic') { return $method['ret']; } } } } } } } } } } } } } } } } return ''; }
function node_func($file, $conditional, $node, $current_scope, $current_class, $namespace = '') { global $scope, $classes; if ($node instanceof \ast\Node) { $req = $opt = 0; $dc = ['return' => '', 'params' => []]; if (!empty($node->docComment)) { $dc = parse_doc_comment($node->docComment); } $result = ['file' => $file, 'namespace' => $namespace, 'scope' => $current_scope, 'conditional' => $conditional, 'flags' => $node->flags, 'lineno' => $node->lineno, 'endLineno' => $node->endLineno, 'name' => strpos($current_scope, '::') === false ? $namespace . $node->name : $node->name, 'docComment' => $node->docComment, 'params' => node_paramlist($file, $node->children[0], $req, $opt, $dc, $namespace), 'required' => $req, 'optional' => $opt, 'ret' => '', 'oret' => '', 'ast' => $node->children[2]]; if (!empty($dc['deprecated'])) { $result['deprecated'] = true; } if ($node->children[3] !== null) { $result['oret'] = ast_node_type($file, $node->children[3], $namespace); // Original return type $result['ret'] = ast_node_type($file, $node->children[3], $namespace); // This one changes as we walk the tree } else { // Check if the docComment has a return value specified if (!empty($dc['return'])) { // We can't actually figure out 'static' at this point, but fill it in regardless. It will be partially correct if ($dc['return'] == 'static' || $dc['return'] == 'self' || $dc['return'] == '$this') { if (strpos($current_scope, '::') !== false) { list($dc['return'], ) = explode('::', $current_scope); } } $result['oret'] = $dc['return']; $result['ret'] = $dc['return']; } } // Add params to local scope for user functions if ($file != 'internal') { $i = 1; foreach ($result['params'] as $k => $v) { if (empty($v['type'])) { // If there is no type specified in PHP, check for a docComment // We assume order in the docComment matches the parameter order in the code if (!empty($dc['params'][$k]['type'])) { $scope[$current_scope]['vars'][$v['name']] = ['type' => $dc['params'][$k]['type'], 'tainted' => false, 'tainted_by' => '', 'param' => $i]; } else { $scope[$current_scope]['vars'][$v['name']] = ['type' => '', 'tainted' => false, 'tainted_by' => '', 'param' => $i]; } } else { $scope[$current_scope]['vars'][$v['name']] = ['type' => $v['type'], 'tainted' => false, 'tainted_by' => '', 'param' => $i]; } if (array_key_exists('def', $v)) { $type = node_type($file, $namespace, $v['def'], $current_scope, empty($current_class) ? null : $classes[strtolower($current_class)]); if ($scope[$current_scope]['vars'][$v['name']]['type'] !== '') { // Does the default value match the declared type? if ($type !== 'null' && !type_check($type, $scope[$current_scope]['vars'][$v['name']]['type'])) { Log::err(Log::ETYPE, "Default value for {$scope[$current_scope]['vars'][$v['name']]['type']} \${$v['name']} can't be {$type}", $file, $node->lineno); } } add_type($current_scope, $v['name'], strtolower($type)); // If we have no other type info about a parameter, just because it has a default value of null // doesn't mean that is its type. Any type can default to null if ($type === 'null' && !empty($result['params'][$k]['type'])) { $result['params'][$k]['type'] = merge_type($result['params'][$k]['type'], strtolower($type)); } } $i++; } if (!empty($dc['vars'])) { foreach ($dc['vars'] as $var) { if (empty($scope[$current_scope]['vars'][$var['name']])) { $scope[$current_scope]['vars'][$var['name']] = ['type' => $var['type'], 'tainted' => false, 'tainted_by' => '']; } else { add_type($current_scope, $var['name'], $var['type']); } } } } return $result; } assert(false, "{$node} was not an \\ast\\Node"); }
function node_type($file, $namespace, $node, $current_scope, $current_class, &$taint = null, $check_var_exists = true) { global $classes, $functions, $scope, $namespace_map, $internal_arginfo; if (!$node instanceof \ast\Node) { if ($node === null) { return ''; } return type_map(gettype($node)); } else { if ($node->kind == \ast\AST_ARRAY) { if (!empty($node->children) && $node->children[0] instanceof \ast\Node && $node->children[0]->kind == \ast\AST_ARRAY_ELEM) { // Check the first 5 (completely arbitrary) elements and assume the rest are the same type $etypes = []; for ($i = 0; $i < 5; $i++) { if (empty($node->children[$i])) { break; } if ($node->children[$i]->children[0] instanceof \ast\Node) { $etypes[] = node_type($file, $namespace, $node->children[$i]->children[0], $current_scope, $current_class, $temp_taint); } else { $etypes[] = type_map(gettype($node->children[$i]->children[0])); } } $types = array_unique($etypes); if (count($types) == 1 && !empty($types[0])) { return mkgenerics($types[0]); } } return 'array'; } else { if ($node->kind == \ast\AST_BINARY_OP || $node->kind == \ast\AST_GREATER || $node->kind == \ast\AST_GREATER_EQUAL) { if ($node->kind == \ast\AST_BINARY_OP) { $node_flags = $node->flags; } else { $node_flags = $node->kind; } $taint = var_taint_check($file, $node, $current_scope); switch ($node_flags) { // Always a string from a concat case \ast\flags\BINARY_CONCAT: $temp_taint = false; node_type($file, $namespace, $node->children[0], $current_scope, $current_class, $temp_taint); if ($temp_taint) { $taint = true; return 'string'; } node_type($file, $namespace, $node->children[1], $current_scope, $current_class, $temp_taint); if ($temp_taint) { $taint = true; } return 'string'; break; // Boolean unless invalid operands // Boolean unless invalid operands case \ast\flags\BINARY_IS_IDENTICAL: case \ast\flags\BINARY_IS_NOT_IDENTICAL: case \ast\flags\BINARY_IS_EQUAL: case \ast\flags\BINARY_IS_NOT_EQUAL: case \ast\flags\BINARY_IS_SMALLER: case \ast\flags\BINARY_IS_SMALLER_OR_EQUAL: case \ast\AST_GREATER: case \ast\AST_GREATER_EQUAL: $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class); if (!$temp) { $left = ''; } else { $left = type_map($temp); } $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class); if (!$temp) { $right = ''; } else { $right = type_map($temp); } $taint = false; // If we have generics and no non-generics on the left and the right is not array-like ... if (!empty(generics($left)) && empty(nongenerics($left)) && !type_check($right, 'array')) { Log::err(Log::ETYPE, "array to {$right} comparison", $file, $node->lineno); } else { // and the same for the right side if (!empty(generics($right)) && empty(nongenerics($right)) && !type_check($left, 'array')) { Log::err(Log::ETYPE, "{$left} to array comparison", $file, $node->lineno); } } return 'bool'; break; // Add is special because you can add arrays // Add is special because you can add arrays case \ast\flags\BINARY_ADD: $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class); if (!$temp) { $left = ''; } else { $left = type_map($temp); } $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class); if (!$temp) { $right = ''; } else { $right = type_map($temp); } // fast-track common cases if ($left == 'int' && $right == 'int') { return 'int'; } if (($left == 'int' || $left == 'float') && ($right == 'int' || $right == 'float')) { return 'float'; } $left_is_array = !empty(generics($left)) && empty(nongenerics($left)); $right_is_array = !empty(generics($right)) && empty(nongenerics($right)); if ($left_is_array && !type_check($right, 'array')) { Log::err(Log::ETYPE, "invalid operator: left operand is array and right is not", $file, $node->lineno); return ''; } else { if ($right_is_array && !type_check($left, 'array')) { Log::err(Log::ETYPE, "invalid operator: right operand is array and left is not", $file, $node->lineno); return ''; } else { if ($left_is_array || $right_is_array) { // If it is a '+' and we know one side is an array and the other is unknown, assume array return 'array'; } } } return 'int|float'; $taint = false; break; // Everything else should be an int/float // Everything else should be an int/float default: $temp = node_type($file, $namespace, $node->children[0], $current_scope, $current_class); if (!$temp) { $left = ''; } else { $left = type_map($temp); } $temp = node_type($file, $namespace, $node->children[1], $current_scope, $current_class); if (!$temp) { $right = ''; } else { $right = type_map($temp); } if ($left == 'array' || $right == 'array') { Log::err(Log::ETYPE, "invalid array operator", $file, $node->lineno); return ''; } else { if ($left == 'int' && $right == 'int') { return 'int'; } else { if ($left == 'float' || $right == 'float') { return 'float'; } } } return 'int|float'; $taint = false; break; } } else { if ($node->kind == \ast\AST_CAST) { $taint = var_taint_check($file, $node->children[0], $current_scope); switch ($node->flags) { case \ast\flags\TYPE_NULL: return 'null'; break; case \ast\flags\TYPE_BOOL: $taint = false; return 'bool'; break; case \ast\flags\TYPE_LONG: $taint = false; return 'int'; break; case \ast\flags\TYPE_DOUBLE: $taint = false; return 'float'; break; case \ast\flags\TYPE_STRING: return 'string'; break; case \ast\flags\TYPE_ARRAY: return 'array'; break; case \ast\flags\TYPE_OBJECT: return 'object'; break; default: Log::err(Log::EFATAL, "Unknown type (" . $node->flags . ") in cast"); } } else { if ($node->kind == \ast\AST_NEW) { $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); if ($class_name) { return $classes[strtolower($class_name)]['type']; } return 'object'; } else { if ($node->kind == \ast\AST_DIM) { $taint = var_taint_check($file, $node->children[0], $current_scope); $type = node_type($file, $namespace, $node->children[0], $current_scope, $current_class); if (!empty($type)) { $gen = generics($type); if (empty($gen)) { if ($type !== 'null' && !type_check($type, 'string|ArrayAccess')) { // array offsets work on strings, unfortunately // Double check that any classes in the type don't have ArrayAccess $ok = false; foreach (explode('|', $type) as $t) { if (!empty($t) && !is_native_type($t)) { if (!empty($classes[strtolower($t)]['type'])) { if (strpos('|' . $classes[strtolower($t)]['type'] . '|', '|ArrayAccess|') !== false) { $ok = true; break; } } } } if (!$ok) { Log::err(Log::ETYPE, "Suspicious array access to {$type}", $file, $node->lineno); } } return ''; } } else { return ''; } return $gen; } else { if ($node->kind == \ast\AST_VAR) { return var_type($file, $node, $current_scope, $taint, $check_var_exists); } else { if ($node->kind == \ast\AST_ENCAPS_LIST) { foreach ($node->children as $encap) { if ($encap instanceof \ast\Node) { if (var_taint_check($file, $encap, $current_scope)) { $taint = true; } } } return "string"; } else { if ($node->kind == \ast\AST_CONST) { if ($node->children[0]->kind == \ast\AST_NAME) { if (defined($node->children[0]->children[0])) { return type_map(gettype(constant($node->children[0]->children[0]))); } else { // Todo: user-defined constant } } } else { if ($node->kind == \ast\AST_CLASS_CONST) { if ($node->children[1] == 'class') { return 'string'; } // class name fetch $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); if (!$class_name) { return ''; } $ltemp = strtolower($class_name); while ($ltemp && !array_key_exists($node->children[1], $classes[$ltemp]['constants'])) { $ltemp = strtolower($classes[$ltemp]['parent']); if (empty($classes[$ltemp])) { return ''; } // undeclared class - will be caught elsewhere } if (!$ltemp || !array_key_exists($node->children[1], $classes[$ltemp]['constants'])) { Log::err(Log::EUNDEF, "can't access undeclared constant {$class_name}::{$node->children[1]}", $file, $node->lineno); return ''; } return $classes[$ltemp]['constants'][$node->children[1]]['type']; } else { if ($node->kind == \ast\AST_PROP) { if ($node->children[0]->kind == \ast\AST_VAR) { $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); if ($class_name && !$node->children[1] instanceof \ast\Node) { $ltemp = find_property($file, $node, $class_name, $node->children[1], $class_name, false); if (empty($ltemp)) { return ''; } return $classes[$ltemp]['properties'][$node->children[1]]['type']; } } } else { if ($node->kind == \ast\AST_STATIC_PROP) { if ($node->children[0]->kind == \ast\AST_NAME) { $class_name = qualified_name($file, $node->children[0], $namespace); if ($class_name && !$node->children[1] instanceof \ast\Node) { $ltemp = find_property($file, $node, $class_name, $node->children[1], $class_name, false); if (empty($ltemp)) { return ''; } return $classes[$ltemp]['properties'][$node->children[1]]['type']; } } } else { if ($node->kind == \ast\AST_CALL) { if ($node->children[0]->kind == \ast\AST_NAME) { $func_name = $node->children[0]->children[0]; if ($node->children[0]->flags & \ast\flags\NAME_NOT_FQ) { $func = $namespace_map[T_FUNCTION][$file][strtolower($namespace . $func_name)] ?? $namespace_map[T_FUNCTION][$file][strtolower($func_name)] ?? $functions[strtolower($namespace . $func_name)] ?? $functions[strtolower($func_name)] ?? null; } else { $func = $functions[strtolower($func_name)] ?? null; } if ($func['file'] == 'internal' && empty($func['ret'])) { if (!empty($internal_arginfo[$func_name])) { return $internal_arginfo[$func_name][0] ?? ''; } } else { return $func['ret'] ?? ''; } } else { // TODO: Handle $func() and other cases that get here } } else { if ($node->kind == \ast\AST_STATIC_CALL) { $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); $method = find_method($class_name, $node->children[1]); if ($method) { return $method['ret'] ?? ''; } } else { if ($node->kind == \ast\AST_METHOD_CALL) { $class_name = find_class_name($file, $node, $namespace, $current_class, $current_scope); if ($class_name) { $method_name = $node->children[1]; $method = find_method($class_name, $method_name); if ($method === false) { Log::err(Log::EUNDEF, "call to undeclared method {$class_name}->{$method_name}()", $file, $node->lineno); } else { if ($method != 'dynamic') { return $method['ret']; } } } } } } } } } } } } } } } } } } return ''; }
function node_func($file, $conditional, $node, $current_scope, $current_class, $namespace = '') { global $scope; if ($node instanceof \ast\Node) { $req = $opt = 0; $dc = ['return' => '', 'params' => []]; if (!empty($node->docComment)) { $dc = parse_doc_comment($node->docComment); } $result = ['file' => $file, 'namespace' => $namespace, 'scope' => $current_scope, 'conditional' => $conditional, 'flags' => $node->flags, 'lineno' => $node->lineno, 'endLineno' => $node->endLineno, 'name' => strpos($current_scope, '::') === false ? $namespace . $node->name : $node->name, 'docComment' => $node->docComment, 'params' => node_paramlist($file, $node->children[0], $req, $opt, $dc, $namespace), 'required' => $req, 'optional' => $opt, 'ret' => '', 'oret' => '', 'ast' => $node->children[2]]; if ($node->children[3] !== null) { $result['oret'] = ast_node_type($file, $node->children[3], $namespace); // Original return type $result['ret'] = ast_node_type($file, $node->children[3], $namespace); // This one changes as we walk the tree } else { // Check if the docComment has a return value specified if (!empty($dc['return'])) { if ($dc['return'] == 'static' || $dc['return'] == 'self' || $dc['return'] == '$this') { if (strpos($current_scope, '::') !== false) { list($dc['return'], ) = explode('::', $current_scope); } } $result['oret'] = $dc['return']; $result['ret'] = $dc['return']; } } // Add params to local scope for user functions if ($file != 'internal') { $i = 1; foreach ($result['params'] as $k => $v) { if (empty($v['type'])) { // If there is no type specified in PHP, check for a docComment // We assume order in the docComment matches the parameter order in the code if (!empty($dc['params'][$k]['type'])) { $scope[$current_scope]['vars'][$v['name']] = ['type' => $dc['params'][$k]['type'], 'tainted' => false, 'tainted_by' => '', 'param' => $i]; } else { $scope[$current_scope]['vars'][$v['name']] = ['type' => '', 'tainted' => false, 'tainted_by' => '', 'param' => $i]; } } else { $scope[$current_scope]['vars'][$v['name']] = ['type' => $v['type'], 'tainted' => false, 'tainted_by' => '', 'param' => $i]; } if (array_key_exists('def', $v)) { $type = node_type($file, $namespace, $v['def'], $current_scope, $current_class); if ($type === "NULL") { add_type($current_scope, $v['name'], $type); if (!empty($result['params'][$k]['type'])) { $result['params'][$k]['type'] .= '|NULL'; } } else { if ($scope[$current_scope]['vars'][$v['name']]['type'] !== '') { // Does the default value match the declared type? if (!type_check($type, $scope[$current_scope]['vars'][$v['name']]['type'])) { Log::err(Log::ETYPE, "Default value for {$scope[$current_scope]['vars'][$v['name']]['type']} \${$v['name']} can't be {$type}", $file, $node->lineno); } } } } $i++; } } return $result; } assert(false, "{$node} was not an \\ast\\Node"); }
function item_display($station, $itemId, $item, $lvl = 0) { global $Db, $hrwidth, $pos_fuels, $xpandall; $type = $Db->getTypeFromTypeId($item["typeID"]); $name = $type["typeName"]; /* New field - rawQuantity Items in the AssetList (and ContractItems) now include a rawQuantity attribute if the quantity in the DB is negative. Negative quantities are in fact codes, -1 indicates that the item is a singleton (non-stackable). If the item happens to be a Blueprint, -1 is an Original and -2 is a Blueprint Copy. For further information about negative quantities see this devblog http://www.eveonline.com/devblog.asp?a=blog&nbid=2324 */ if (strpos($name, " Blueprint") !== false && isset($item["rawQuantity"]) && (int) $item["rawQuantity"] == -2) { $name .= " (Copy)"; } $output = ""; if ($pos_fuels && !contains_pos_fuel($item)) { return; } if ($type == "") { $name = "<b><span style='font-size:80%;'>[UNKNOWN ITEM TYPE " . $item["typeID"] . "]</span></b>"; } else { type_check($type, $station); } $space = ""; for ($i = 0; $i < $lvl; $i++) { $space .= " "; } if (isset($item["contents"])) { $output .= "{$space}<a href=\"#\" onclick=\"return toggle_visibility('i" . $itemId . "');\">"; $output .= "{$name} [" . count($item["contents"]) . "]</a><br>\n"; $output .= "<div id=\"i" . $itemId . "\" style=\"" . ($xpandall && (!$pos_fuels || !is_space($item)) ? "display:block;" : "display:none;") . "\">\n<HR align=left>\n"; foreach ($item["contents"] as $itemIdC => $itemC) { $output .= item_display($station, $itemIdC, $itemC, $lvl + 1); } $output .= "<HR align=left></div>"; } else { if (!$pos_fuels || $pos_fuels && is_pos_fuel($type)) { $output .= $space . ($item["quantity"] == "1" ? "" : $item["quantity"] . " x ") . $name . "<br>\n"; } } return $output; }
function arglist_type_check($file, $arglist, $func, $current_scope) : array { global $internal_arginfo, $scope, $tainted_by; $errs = []; $fn = $func['scope'] ?? $func['name']; foreach ($arglist->children as $k => $arg) { $taint = false; $tainted_by = ''; if (empty($func['params'][$k])) { break; } $param = $func['params'][$k]; $argno = $k + 1; $arg_name = false; if ($param['flags'] & \ast\flags\PARAM_REF) { if (!$arg instanceof \ast\Node || $arg->kind != \ast\AST_VAR && $arg->kind != \ast\AST_DIM && $arg->kind != \ast\AST_PROP) { $errs[] = "Only variables can be passed by reference at arg#{$argno} of {$fn}()"; } else { $arg_name = var_name($arg); } } // For user functions, add the types of the args to the receiving function's scope if ($func['file'] != 'internal') { if (empty($scope[$fn]['vars'][$param['name']])) { $scope[$fn]['vars'][$param['name']] = ['type' => '', 'tainted' => false, 'tainted_by' => '']; } // If it is by-ref link it back to the local variable name if ($param['flags'] & \ast\flags\PARAM_REF) { $arg_type = node_type($file, $arg, $current_scope, $taint, false); if (!empty($scope[$current_scope]['vars'][$arg_name])) { $scope[$fn]['vars'][$param['name']] =& $scope[$current_scope]['vars'][$arg_name]; } else { $scope[$fn]['vars'][$param['name']]['type'] = $arg_type; } } else { $arg_type = node_type($file, $arg, $current_scope, $taint); if (!empty($arg_type)) { add_type($fn, $param['name'], $arg_type); } } if ($taint) { $scope[$fn]['vars'][$param['name']]['tainted'] = true; $scope[$fn]['vars'][$param['name']]['tainted_by'] = $tainted_by; } else { $scope[$fn]['vars'][$param['name']]['tainted'] = false; $scope[$fn]['vars'][$param['name']]['tainted_by'] = ''; } } else { $arg_type = node_type($file, $arg, $current_scope, $taint, !($param['flags'] & \ast\flags\PARAM_REF)); } // For all functions, add the param to the local scope if pass-by-ref // and make it an actual ref for user functions if ($param['flags'] & \ast\flags\PARAM_REF) { if ($func['file'] == 'internal') { if (empty($scope[$current_scope]['vars'][$arg_name])) { add_var_scope($current_scope, $arg_name, $arg_type); } } else { if (empty($scope[$current_scope]['vars'][$arg_name])) { if (!array_key_exists($current_scope, $scope)) { $scope[$current_scope] = []; } if (!array_key_exists('vars', $scope[$current_scope])) { $scope[$current_scope]['vars'] = []; } $scope[$current_scope]['vars'][$arg_name] =& $scope[$fn]['vars'][$param['name']]; } } } // TODO: specific object checks here. this is turning object:name into just object // (and also callable:{closure n} into just callable if (strpos($arg_type, ':') !== false) { list($arg_type, ) = explode(':', $arg_type, 2); } if (!type_check($arg_type, $param['type'])) { if (!empty($param['name'])) { $paramstr = '(' . trim($param['name'], '&=') . ')'; } else { $paramstr = ''; } if (empty($arg_type)) { $arg_type = 'mixed'; } if ($func['file'] == 'internal') { $errs[] = "arg#{$argno}{$paramstr} is {$arg_type} but {$func['name']}() takes {$param['type']}"; } else { $errs[] = "arg#{$argno}{$paramstr} is {$arg_type} but {$func['name']}() takes {$param['type']} defined at {$func['file']}:{$func['lineno']}"; } } } return $errs; }
function writeSI() { $index = array(); $this->stringIndex = gather_strings($this->payload, $index, $this->payload); if (count($this->stringIndex) === 0) { $this->writeValue(EMPTYINDEX, CHAR, TRUE); return $this; } $this->stringIndexType = type_check(count($this->stringIndex)); $this->writeValue(count($this->stringIndex), $this->stringIndexType); foreach ($this->stringIndex as $s) { $this->writeString($s); } return $this; }
function node_type($file, $namespace, $node, $current_scope, &$taint = null, $check_var_exists = true) { global $classes, $functions, $namespace_map, $internal_arginfo; if (!$node instanceof \ast\Node) { if ($node === null) { return ''; } return type_map(gettype($node)); } else { if ($node->kind == \ast\AST_ARRAY) { return 'array'; } else { if ($node->kind == \ast\AST_BINARY_OP) { $taint = var_taint_check($file, $node, $current_scope); switch ($node->flags) { // Always a string from a concat case \ast\flags\BINARY_CONCAT: $temp_taint = false; node_type($file, $namespace, $node->children[0], $current_scope, $temp_taint); if ($temp_taint) { $taint = true; return 'string'; } node_type($file, $namespace, $node->children[1], $current_scope, $temp_taint); if ($temp_taint) { $taint = true; } return 'string'; break; // Boolean unless invalid operands // Boolean unless invalid operands case \ast\flags\BINARY_IS_IDENTICAL: case \ast\flags\BINARY_IS_NOT_IDENTICAL: case \ast\flags\BINARY_IS_EQUAL: case \ast\flags\BINARY_IS_NOT_EQUAL: case \ast\flags\BINARY_IS_SMALLER: case \ast\flags\BINARY_IS_SMALLER_OR_EQUAL: $taint = false; return 'bool'; break; // Everything else should be an int/float // Everything else should be an int/float default: $left = type_map(node_type($file, $namespace, $node->children[0], $current_scope)); $right = type_map(node_type($file, $namespace, $node->children[1], $current_scope)); if ($left == 'array' && $right == 'array') { return 'array'; } else { if ($left == 'array' && !type_check($right, 'array')) { Log::err(Log::ETYPE, "invalid operator: left operand is array and right is not", $file, $node->lineno); return 'mixed'; } else { if ($right == 'array' && !type_check($left, 'array')) { Log::err(Log::ETYPE, "invalid operator: right operand is array and left is not", $file, $node->lineno); return 'mixed'; } else { if ($left == 'int' && $right == 'int') { return 'int'; } else { if ($left == 'float' || $right == 'float') { return 'float'; } } } } } return 'int|float'; $taint = false; break; } } else { if ($node->kind == \ast\AST_CAST) { $taint = var_taint_check($file, $node->children[0], $current_scope); switch ($node->flags) { case \ast\flags\TYPE_NULL: return 'null'; break; case \ast\flags\TYPE_BOOL: $taint = false; return 'bool'; break; case \ast\flags\TYPE_LONG: $taint = false; return 'int'; break; case \ast\flags\TYPE_DOUBLE: $taint = false; return 'float'; break; case \ast\flags\TYPE_STRING: return 'string'; break; case \ast\flags\TYPE_ARRAY: return 'array'; break; case \ast\flags\TYPE_OBJECT: return 'stdClass'; break; default: Log::err(Log::EFATAL, "Unknown type (" . $node->flags . ") in cast"); } } else { if ($node->kind == \ast\AST_NEW) { if ($node->children[0]->kind == \ast\AST_NAME) { $name = var_name($node->children[0]); if ($name == 'self' || $name == 'static') { list($class, ) = explode('::', $current_scope); $name = $class; } $found = null; if ($node->children[0]->flags & \ast\flags\NAME_NOT_FQ) { if (!empty($namespace_map[T_CLASS][$file][strtolower($name)])) { if (!empty($classes[strtolower($namespace_map[T_CLASS][$file][strtolower($name)])])) { $found = $classes[strtolower($namespace_map[T_CLASS][$file][strtolower($name)])]; } } if (!$found && empty($classes[strtolower($namespace . $name)]) && empty($classes[strtolower($name)])) { Log::err(Log::EUNDEF, "Trying to instantiate undeclared class {$name}", $file, $node->lineno); } $found = $found ?? $classes[strtolower($namespace . $name)] ?? $classes[strtolower($name)] ?? null; } else { if (empty($classes[strtolower($name)])) { Log::err(Log::EUNDEF, "Trying to instantiate undeclared class {$name}", $file, $node->lineno); } else { $found = $classes[strtolower($name)]; } } if ($found) { return $found['type']; } else { return $name; } } return 'object'; } else { if ($node->kind == \ast\AST_DIM) { $taint = var_taint_check($file, $node->children[0], $current_scope); // TODO: Do something smart with array elements return 'mixed'; } else { if ($node->kind == \ast\AST_VAR) { return var_type($file, $node, $current_scope, $taint, $check_var_exists); } else { if ($node->kind == \ast\AST_ENCAPS_LIST) { foreach ($node->children as $encap) { if ($encap instanceof \ast\Node) { if (var_taint_check($file, $encap, $current_scope)) { $taint = true; } } } return "string"; } else { if ($node->kind == \ast\AST_CONST) { if ($node->children[0]->kind == \ast\AST_NAME) { if (defined($node->children[0]->children[0])) { return type_map(gettype(constant($node->children[0]->children[0]))); } else { // User-defined constant } } } else { if ($node->kind == \ast\AST_CALL) { if ($node->children[0]->kind == \ast\AST_NAME) { $func_name = $node->children[0]->children[0]; if ($node->children[0]->flags & \ast\flags\NAME_NOT_FQ) { $func = $namespace_map[T_FUNCTION][$file][strtolower($namespace . $func_name)] ?? $namespace_map[T_FUNCTION][$file][strtolower($func_name)] ?? $functions[strtolower($namespace . $func_name)] ?? $functions[strtolower($func_name)] ?? null; } else { $func = $functions[strtolower($func_name)] ?? null; } if ($func['file'] == 'internal' && empty($func['ret'])) { if (!empty($internal_arginfo[$func_name])) { return $internal_arginfo[$func_name][0] ?? 'mixed'; } } else { return $func['ret'] ?? 'mixed'; } } else { // TODO: Handle $func() and other cases that get here } } else { if ($node->kind == \ast\AST_STATIC_CALL) { // If the called static method has a return type defined, use that $call = $node->children[0]; if ($call->kind == \ast\AST_NAME) { // Simple static function call $class_name = $call->children[0]; $method_name = $node->children[1]; $class = find_class($call, $namespace, $namespace_map[T_CLASS][$file] ?? []); if (!$class) { Log::err(Log::EUNDEF, "static call to undeclared class {$class_name}", $file, $call->lineno); return 'mixed'; } $method = find_method($class['name'], $method_name); if ($method) { return $method['ret'] ?? 'mixed'; } } else { // Weird dynamic static call - give up return 'mixed'; } } } } } } } } } } } } return "mixed"; }
static function toTemplate($payload) { $type = type_check($payload); if ($type === 0x80) { return array(type_gcd($payload)); } else { if ($type === 0x9) { $ret = array(); foreach ($payload as $k => $v) { $ret[$k] = self::toTemplate($v); } return $ret; } else { if ($type === 0x1b) { $ret = array(); foreach (get_object_vars($payload) as $k => $v) { $ret[$k] = self::toTemplate($v); } return $ret; } else { if ($type === 0x21) { return 0x20; } else { return $type; } } } } }