invoke-custom
Starting from DEX version 038, the invoke-custom instruction was introduced to enable flexible, dynamic invocation of methods. It plays a crucial role in dynamic method handling, particularly useful for lambda expressions and functional programming in Java and allows Dalvik VM to dynamically call methods at runtime by referencing a “call site”, which points to specific runtime metadata defining the method to be called. In simple terms, invoke-custom makes it possible to execute methods that are not directly known or accessible at compile time but are defined dynamically when the application runs. This dynamic invocation mechanism aligns with Java’s java.lang.invoke API and provides key support for features like lambda expressions.
Instruction Syntax and Structure
Here’s a basic outline of the syntax:
invoke-custom {vC, vD, vE, vF, vG}, call_site@BBBB
{vC, vD, vE, vF, vG}: Registers containing arguments passed to the invoked method.call_site@BBBB: Reference to a “call site” in the DEX file (identified by the indexBBBB), which points to a method and the metadata required for invocation.
Each invoke-custom instruction has the following fields:
- Argument word count (4 bits): Specifies the number of arguments for the method.
- Call site reference index (16 bits): An index pointing to the
call_site_itemin the DEX file, which contains all the metadata for the invocation. - Argument registers: Up to five registers that hold the method’s arguments.
The Role of call_site_item
call_site_item is a special DEX structure used to hold metadata about a call site. It’s located in the DEX file’s data section and contains details about the method to be invoked, including the following:
- Bootstrap Method Handle: A reference to a “bootstrap” method that sets up the call site. This method typically belongs to classes like
LambdaMetafactoryin Java and must return ajava.lang.invoke.CallSite. - Method Name: A string representing the name of the method to be resolved.
- Method Type: A descriptor defining the method’s argument and return types.
This setup gives invoke-custom the information needed to invoke a method dynamically.
Example of a call_site_item
Here’s what a typical call_site_item would look like conceptually:
{
bootstrap_method: VALUE_METHOD_HANDLE (e.g., LambdaMetafactory.metafactory),
method_name: VALUE_STRING (e.g., "isSplitMetaElement"),
method_type: VALUE_METHOD_TYPE (e.g., (Lcom/reandroid/arsc/chunk/xml/ResXmlElement;)Z)
}
Additional arguments can also be included as constant values, passed to the bootstrap method in order.
Execution Phases of invoke-custom
The invoke-custom instruction executes in two main phases:
Call Site Resolution:
- The DEX interpreter checks if there’s an existing
CallSiteobject for the specified call site. - If a
CallSitedoesn’t exist, the bootstrap method specified incall_site_itemis invoked with the provided arguments. This bootstrap method returns aCallSiteobject that represents the method to be called. - The
CallSiteobject is cached for future calls to improve performance.
- The DEX interpreter checks if there’s an existing
Call Site Invocation:
- After the
CallSiteis resolved, theMethodHandlewithin theCallSiteis invoked. - This invocation works similarly to the
invoke-polymorphicinstruction, where the arguments passed in theinvoke-custominstruction are used directly.
- After the
Practical Example: invoke-custom in Action
Consider this Java code in the ApkSplitInfoCleaner class:
FilterIterator.of(manifest.recursiveElements(), ApkSplitInfoCleaner::isSplitMetaElement)
In bytecode, this invocation of isSplitMetaElement as a lambda will be compiled into an invoke-custom call, where the DEX file contains a call_site_item that references LambdaMetafactory.metafactory to create a lambda expression.
Bytecode Example
Here’s what this might look like in Dalvik bytecode:
invoke-custom {}, call_site_2("test", ()Ljava/util/function/Predicate;, (Ljava/lang/Object;)Z,
invoke-static@Lcom/reandroid/apk/ApkSplitInfoCleaner;->isSplitMetaElement(Lcom/reandroid/arsc/chunk/xml/ResXmlElement;)Z,
(Lcom/reandroid/arsc/chunk/xml/ResXmlElement;)Z)@Ljava/lang/invoke/LambdaMetafactory;->metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
This example showcases the following:
call_site_2defines the arguments for theLambdaMetafactory.metafactorybootstrap method.- The bootstrap method generates a
CallSiteinstance, enabling theisSplitMetaElementmethod to be invoked as a lambda expression matching a functional interface.
invoke-polymorphic
invoke-polymorphic is another bytecode instruction (Also introduced starting from DEX version 038 onwards) similar to invoke-custom, but it’s specifically used for invoking MethodHandle methods like invoke or invokeExact. While invoke-custom is for more general dynamic invocation, invoke-polymorphic is restricted to methods directly supporting polymorphic invocation in MethodHandle.