blob: 6aa5de21168c299f8a137831eda8aafe9997ff75 [file] [log] [blame]
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Defines the actions various bytecodes have on the frame.
Each function here corresponds to a bytecode documented in
https://docs.python.org/2/library/dis.html. The first argument is a (mutable)
FrameState object, the second the integer opcode argument.
Bytecodes with more complicated behavior (e.g. modifying the program counter)
are handled inline rather than here.
For internal use only; no backwards-compatibility guarantees.
"""
from __future__ import absolute_import
import inspect
import logging
import sys
import types
from functools import reduce
from past.builtins import unicode
from . import typehints
from .trivial_inference import BoundMethod
from .trivial_inference import Const
from .trivial_inference import element_type
from .trivial_inference import union
from .typehints import Any
from .typehints import Dict
from .typehints import Iterable
from .typehints import List
from .typehints import Tuple
from .typehints import Union
def pop_one(state, unused_arg):
del state.stack[-1:]
def pop_two(state, unused_arg):
del state.stack[-2:]
def pop_three(state, unused_arg):
del state.stack[-3:]
def push_value(v):
def pusher(state, unused_arg):
state.stack.append(v)
return pusher
def nop(unused_state, unused_arg):
pass
def pop_top(state, unused_arg):
state.stack.pop()
def rot_n(state, n):
state.stack[-n:] = [state.stack[-1]] + state.stack[-n:-1]
def rot_two(state, unused_arg):
rot_n(state, 2)
def rot_three(state, unused_arg):
rot_n(state, 3)
def rot_four(state, unused_arg):
rot_n(state, 4)
def dup_top(state, unused_arg):
state.stack.append(state.stack[-1])
def unary(state, unused_arg):
state.stack[-1] = Const.unwrap(state.stack[-1])
unary_positive = unary_negative = unary_invert = unary
def unary_not(state, unused_arg):
state.stack[-1] = bool
def unary_convert(state, unused_arg):
state.stack[-1] = str
def get_iter(state, unused_arg):
state.stack.append(Iterable[element_type(state.stack.pop())])
def symmetric_binary_op(state, unused_arg):
# TODO(robertwb): This may not be entirely correct...
b, a = state.stack.pop(), state.stack.pop()
if a == b:
state.stack.append(a)
elif type(a) == type(b) and isinstance(a, typehints.SequenceTypeConstraint):
state.stack.append(type(a)(union(element_type(a), element_type(b))))
else:
state.stack.append(Any)
# Except for int ** -int
binary_power = inplace_power = symmetric_binary_op
binary_multiply = inplace_multiply = symmetric_binary_op
binary_divide = inplace_divide = symmetric_binary_op
binary_floor_divide = inplace_floor_divide = symmetric_binary_op
def binary_true_divide(state, unused_arg):
u = union(state.stack.pop(), state.stack.pop)
if u == int:
state.stack.append(float)
else:
state.stack.append(u)
inplace_true_divide = binary_true_divide
binary_modulo = inplace_modulo = symmetric_binary_op
# TODO(robertwb): Tuple add.
binary_add = inplace_add = symmetric_binary_op
binary_subtract = inplace_subtract = symmetric_binary_op
def binary_subscr(state, unused_arg):
index = state.stack.pop()
base = state.stack.pop()
if base in (str, unicode):
out = base
elif (isinstance(index, Const) and isinstance(index.value, int)
and isinstance(base, typehints.IndexableTypeConstraint)):
try:
out = base._constraint_for_index(index.value)
except IndexError:
out = element_type(base)
elif index == slice and isinstance(base, typehints.IndexableTypeConstraint):
out = base
else:
out = element_type(base)
state.stack.append(out)
# As far as types are concerned.
binary_lshift = inplace_lshift = binary_rshift = inplace_rshift = pop_top
binary_and = inplace_and = symmetric_binary_op
binary_xor = inplace_xor = symmetric_binary_op
binary_or = inpalce_or = symmetric_binary_op
# As far as types are concerned.
slice_0 = nop
slice_1 = slice_2 = pop_top
slice_3 = pop_two
store_slice_0 = store_slice_1 = store_slice_2 = store_slice_3 = nop
delete_slice_0 = delete_slice_1 = delete_slice_2 = delete_slice_3 = nop
def store_subscr(unused_state, unused_args):
# TODO(robertwb): Update element/value type of iterable/dict.
pass
binary_divide = binary_floor_divide = binary_modulo = symmetric_binary_op
binary_divide = binary_floor_divide = binary_modulo = symmetric_binary_op
binary_divide = binary_floor_divide = binary_modulo = symmetric_binary_op
# print_expr
print_item = pop_top
# print_item_to
print_newline = nop
# print_newline_to
# break_loop
# continue_loop
def list_append(state, arg):
new_element_type = Const.unwrap(state.stack.pop())
state.stack[-arg] = List[Union[element_type(state.stack[-arg]),
new_element_type]]
def map_add(state, arg):
new_key_type = Const.unwrap(state.stack.pop())
new_value_type = Const.unwrap(state.stack.pop())
state.stack[-arg] = Dict[
Union[state.stack[-arg].key_type, new_key_type],
Union[state.stack[-arg].value_type, new_value_type]]
load_locals = push_value(Dict[str, Any])
# return_value
# yield_value
# import_star
exec_stmt = pop_three
# pop_block
# end_finally
build_class = pop_three
# setup_with
# with_cleanup
# store_name
# delete_name
def unpack_sequence(state, arg):
t = state.stack.pop()
if isinstance(t, Const):
try:
unpacked = [Const(ti) for ti in t.value]
if len(unpacked) != arg:
unpacked = [Any] * arg
except TypeError:
unpacked = [Any] * arg
elif (isinstance(t, typehints.TupleHint.TupleConstraint)
and len(t.tuple_types) == arg):
unpacked = list(t.tuple_types)
else:
unpacked = [element_type(t)] * arg
state.stack += reversed(unpacked)
def dup_topx(state, arg):
state.stack += state[-arg:]
store_attr = pop_top
delete_attr = nop
store_global = pop_top
delete_global = nop
def load_const(state, arg):
state.stack.append(state.const_type(arg))
load_name = push_value(Any)
def build_tuple(state, arg):
if arg == 0:
state.stack.append(Tuple[()])
else:
state.stack[-arg:] = [Tuple[[Const.unwrap(t) for t in state.stack[-arg:]]]]
def build_list(state, arg):
if arg == 0:
state.stack.append(List[Union[()]])
else:
state.stack[-arg:] = [List[reduce(union, state.stack[-arg:], Union[()])]]
# A Dict[Union[], Union[]] is the type of an empty dict.
def build_map(state, unused_arg):
state.stack.append(Dict[Union[()], Union[()]])
def load_attr(state, arg):
"""Replaces the top of the stack, TOS, with
getattr(TOS, co_names[arg])
"""
o = state.stack.pop()
name = state.get_name(arg)
if isinstance(o, Const) and hasattr(o.value, name):
state.stack.append(Const(getattr(o.value, name)))
elif (inspect.isclass(o) and
isinstance(getattr(o, name, None),
(types.MethodType, types.FunctionType))):
# TODO(luke-zhu): Support other callable objects
if sys.version_info[0] == 2:
func = getattr(o, name).__func__
else:
func = getattr(o, name) # Python 3 has no unbound methods
state.stack.append(Const(BoundMethod(func, o)))
else:
state.stack.append(Any)
def load_method(state, arg):
"""Like load_attr. Replaces TOS object with method and TOS."""
o = state.stack.pop()
name = state.get_name(arg)
if isinstance(o, Const):
method = Const(getattr(o.value, name))
elif isinstance(o, typehints.AnyTypeConstraint):
method = typehints.Any
else:
method = Const(BoundMethod(getattr(o, name), o))
state.stack.append(method)
def compare_op(state, unused_arg):
# Could really be anything...
state.stack[-2:] = [bool]
def import_name(state, unused_arg):
state.stack[-2:] = [Any]
import_from = push_value(Any)
# jump
# for_iter
def load_global(state, arg):
state.stack.append(state.get_global(arg))
# setup_loop
# setup_except
# setup_finally
store_map = pop_two
def load_fast(state, arg):
state.stack.append(state.vars[arg])
def store_fast(state, arg):
state.vars[arg] = state.stack.pop()
def delete_fast(state, arg):
state.vars[arg] = Any # really an error
def load_closure(state, arg):
state.stack.append(state.get_closure(arg))
def load_deref(state, arg):
state.stack.append(state.closure_type(arg))
# raise_varargs
def make_function(state, arg):
"""Creates a function with the arguments at the top of the stack.
"""
# TODO(luke-zhu): Handle default argument types
globals = state.f.__globals__ # Inherits globals from the current frame
if sys.version_info[0] == 2:
func_code = state.stack[-1].value
func = types.FunctionType(func_code, globals)
# argc is the number of default parameters. Ignored here.
pop_count = 1 + arg
else: # Python 3.x
func_name = state.stack[-1].value
func_code = state.stack[-2].value
pop_count = 2
closure = None
if sys.version_info[:2] == (3, 5):
# https://docs.python.org/3.5/library/dis.html#opcode-MAKE_FUNCTION
num_default_pos_args = (arg & 0xff)
num_default_kwonly_args = ((arg >> 8) & 0xff)
num_annotations = ((arg >> 16) & 0x7fff)
pop_count += (num_default_pos_args + 2 * num_default_kwonly_args +
num_annotations + num_annotations > 0)
elif sys.version_info >= (3, 6):
# arg contains flags, with corresponding stack values if positive.
# https://docs.python.org/3.6/library/dis.html#opcode-MAKE_FUNCTION
pop_count += bin(arg).count('1')
if arg & 0x08:
# Convert types in Tuple constraint to a tuple of CPython cells.
# https://stackoverflow.com/a/44670295
closure = tuple(
(lambda _: lambda: _)(t).__closure__[0]
for t in state.stack[-3].tuple_types)
func = types.FunctionType(func_code, globals, name=func_name,
closure=closure)
assert pop_count <= len(state.stack)
state.stack[-pop_count:] = [Const(func)]
def make_closure(state, arg):
state.stack[-arg - 2:] = [Any] # a callable
def build_slice(state, arg):
state.stack[-arg:] = [slice] # a slice object
def _unpack_lists(state, arg):
"""Extract inner types of Lists and Tuples.
Pops arg count items from the stack, concatenates their inner types into 1
list, and returns that list.
Example: if stack[-arg:] == [[i1, i2], [i3]], the output is [i1, i2, i3]
"""
types = []
for i in range(arg, 0, -1):
type_constraint = state.stack[-i]
if isinstance(type_constraint, typehints.IndexableTypeConstraint):
types.extend(type_constraint._inner_types())
else:
logging.debug('Unhandled type_constraint: %r', type_constraint)
types.append(typehints.Any)
state.stack[-arg:] = []
return types
def build_list_unpack(state, arg):
"""Joins arg count iterables from the stack into a single list."""
state.stack.append(List[Union[_unpack_lists(state, arg)]])
def build_tuple_unpack(state, arg):
"""Joins arg count iterables from the stack into a single tuple."""
state.stack.append(Tuple[_unpack_lists(state, arg)])
def build_tuple_unpack_with_call(state, arg):
"""Same as build_tuple_unpack, with an extra fn argument at the bottom of the
stack, which remains untouched."""
build_tuple_unpack(state, arg)