Skip to content

Commit

Permalink
Rework lambdas further to actually generate code (of a sort)
Browse files Browse the repository at this point in the history
The generated code lets us use _PIT.operations, which will then call _PIT._handle_invoke properly.
  • Loading branch information
Pokechu22 committed Feb 26, 2021
1 parent d3f7e24 commit fe54914
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 5 deletions.
20 changes: 16 additions & 4 deletions burger/toppings/packetinstructions.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ def _handle_invoke(classloader, classes, instruction, verbose,
"""

num_arguments = len(desc.args)
assert len(stack) >= num_arguments
if num_arguments > 0:
arguments = stack[-num_arguments:]
else:
Expand Down Expand Up @@ -701,6 +702,7 @@ def _handle_3_arg_buffer_call(classloader, classes, instruction, verbose,
classloader, classes, instruction, verbose,
val_info, [instance, "itv.getValue()"]
)
# Same jank as with the one in _handle_2_arg_buffer_call
operations.append(Operation(instruction.pos + 1 - SUB_INS_EPSILON, "endloop"))
return operations
else:
Expand Down Expand Up @@ -790,10 +792,20 @@ def _lambda_operations(classloader, classes, instruction, verbose, info, args):
# case (as that'd require reordering them to make `this` first).
assert len(info.stored_args) == 0 or len(info.stored_args) == 1

return _PIT._sub_operations(
classloader, classes, instruction, verbose, info.method_class,
info.method_name, info.method_desc, effective_args
)
# Now just call the (generated) method.
# Note that info is included because this is
cf, method = info.create_method()
operations = _PIT.operations(classloader, cf, classes, verbose,
method, effective_args)

position = 0
# See note in _sub_operations
for operation in _PIT.ordered_operations(operations):
position += SUB_INS_EPSILON
assert(position < 1)
operation.position = instruction.pos + (position)

return operations

@staticmethod
def format(operations):
Expand Down
95 changes: 94 additions & 1 deletion burger/util.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from abc import ABC, abstractmethod

from jawa.assemble import assemble
from jawa.cf import ClassFile
from jawa.methods import Method
from jawa.constants import *
from jawa.util.descriptor import method_descriptor
from jawa.util.bytecode import Operand
Expand All @@ -17,12 +20,17 @@
REF_newInvokeSpecial = 8
REF_invokeInterface = 9

FIELD_REFS = (REF_getField, REF_getStatic, REF_putField, REF_putStatic)

class InvokeDynamicInfo:
"""
Stores information related to an invokedynamic instruction.
"""

def __init__(self, ins, cf):
self._ins = ins
self._cf = cf

if isinstance(ins.operands[0], Operand):
# Hack due to packetinstructions not expanding constants
const = cf.constants[ins.operands[0].value]
Expand Down Expand Up @@ -73,7 +81,7 @@ class Example {
assert self.ref_kind >= REF_getField and self.ref_kind <= REF_invokeInterface
# Javac does not appear to use REF_getField, REF_getStatic,
# REF_putField, or REF_putStatic, so don't bother handling fields here.
assert self.ref_kind not in (REF_getField, REF_getStatic, REF_putField, REF_putStatic)
assert self.ref_kind not in FIELD_REFS

self.method_class = methodhandle.reference.class_.name.value
self.method_name = methodhandle.reference.name_and_type.name.value
Expand Down Expand Up @@ -170,6 +178,7 @@ class Example {
self.dynamic_desc = method_descriptor(const.name_and_type.descriptor.value)

assert self.dynamic_desc.returns.name != "void"
self.implemented_iface = self.dynamic_desc.returns.name

# created_type is the type returned by the function we return.
if self.ref_kind == REF_newInvokeSpecial:
Expand All @@ -178,6 +187,8 @@ class Example {
self.created_type = self.method_desc.returns.name

self.stored_args = None
self.generated_cf = None
self.generated_method = None

def apply_to_stack(self, stack):
"""
Expand All @@ -200,6 +211,88 @@ def __str__(self):
# TODO: be closer to Java syntax (using the stored args)
return "%s::%s" % (self.method_class, self.method_name)

def create_method(self):
"""
Creates a Method that corresponds to the generated function call.
It will be part of a class that implements the right interface, and will
have the appropriate name and signature.
"""
assert self.stored_args != None
if self.generated_method != None:
return (self.generated_cf, self.generated_method)

class_name = self._cf.this.name.value + "_lambda_" + str(self._ins.pos)
self.generated_cf = ClassFile.create(class_name)
# Jawa doesn't seem to expose this cleanly. Technically we don't need
# to implement the interface because the caller doesn't actually care,
# but it's better to implement it anyways for the future.
# (Due to the hacks below, the interface isn't even implemented properly
# since the method we create has additional parameters and is static.)
iface_const = self.generated_cf.constants.create_class(self.implemented_iface)
self.generated_cf._interfaces.append(iface_const.index)

# HACK: This officially should use instantiated_desc.descriptor,
# but instead use a combination of the stored arguments and the
# instantiated descriptor to make packetinstructions work better
# (otherwise we'd need to generate and load fields in a way that
# packetinstructions understands)
descriptor = "(" + self.dynamic_desc.args_descriptor + \
self.instantiated_desc.args_descriptor + ")" + \
self.instantiated_desc.returns_descriptor
method = self.generated_cf.methods.create(self.dynamic_name,
descriptor, code=True)
self.generated_method = method
# Similar hack: make the method static, so that packetinstructions
# doesn't look for the corresponding instance.
method.access_flags.acc_static = True
# Third hack: the extra arguments are in the local variables/arguments
# list, not on the stack. So we need to move them to the stack.
# (In a real implementation, these would probably be getfield instructions)
# Also, this uses aload for everything, instead of using the appropriate
# instruction for each type.
instructions = []
for i in range(len(method.args)):
instructions.append(("aload", i))

cls_ref = self.generated_cf.constants.create_class(self.method_class)
if self.ref_kind in FIELD_REFS:
# This case is not currently hit, but provided for future use
# (Likely method_name and method_descriptor would no longer be used though)
ref = self.generated_cf.constants.create_field_ref(
self.method_class, self.method_name, self.method_desc.descriptor)
elif self.ref_kind == REF_invokeInterface:
ref = self.generated_cf.constants.create_interface_method_ref(
self.method_class, self.method_name, self.method_desc.descriptor)
else:
ref = self.generated_cf.constants.create_method_ref(
self.method_class, self.method_name, self.method_desc.descriptor)

# See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.5
if self.ref_kind == REF_getField:
instructions.append(("getfield", ref))
elif self.ref_kind == REF_getStatic:
instructions.append(("getstatic", ref))
elif self.ref_kind == REF_putField:
instructions.append(("putfield", ref))
elif self.ref_kind == REF_putStatic:
instructions.append(("putstatic", ref))
elif self.ref_kind == REF_invokeVirtual:
instructions.append(("invokevirtual", ref))
elif self.ref_kind == REF_invokeStatic:
instructions.append(("invokestatic", ref))
elif self.ref_kind == REF_invokeSpecial:
instructions.append(("invokespecial", ref))
elif self.ref_kind == REF_newInvokeSpecial:
instructions.append(("new", cls_ref))
instructions.append(("dup",))
instructions.append(("invokespecial", ref))
elif self.ref_kind == REF_invokeInterface:
instructions.append(("invokeinterface", ref))

method.code.assemble(assemble(instructions))

return (self.generated_cf, self.generated_method)

def class_from_invokedynamic(ins, cf):
"""
Gets the class type for an invokedynamic instruction that
Expand Down

0 comments on commit fe54914

Please sign in to comment.