定位
@At注解的value属性只有以下的有限取值,这个取值也限定了args、target、ordinal、opcode属性的取值:
表示注入到方法的开头,对应的注入方法在目标方法最开始被调用。
args: 无target: 无ordinal: 无opcode: 无
表示注入到RETURN操作符之前。
args: 无target: 无ordinal: 指定注入到哪个RETURN操作符,RETURN计数从0开始。如果不指定,表示注入到所有RETURN操作符之前opcode: 无
对于@Inject而且,这是注入构造方法时唯一一个允许注入的地方。如果注入到一个带返回值的方法中,则调用CallbackInfoReturnable::getReturnType()会得到目标方法即将返回的值,在其他地方注入时调用此方法则会返回null
表示注入到最后一个RETURN操作符之前。
args: 无target: 无ordinal: 无opcode: 无
注意,最后一个RETURN操作符有时并不等同于Java代码中最后一个return关键词,你可能需要用某些查看字节码的IDE插件确认
表示注入到一个方法被调用之前。
args: 允许下列参数:log: 一个布尔值,填log=true则表示会输出相关的日志信息
target: 指定一个带完整限定名的方法签名,如果不指定,则表示匹配所有的方法调用,以下写法是合法的:org.lwjgl.opengl.Display.setTitle(Ljava/lang/String;)Vorg/lwjgl/opengl/Display.setTitle(Ljava/lang/String;)VLorg/lwjgl/opengl/Display;setTitle(Ljava/lang/String;)V
ordinal: 指定注入到哪个被target匹配的目标前,计数从0开始。如果不指定,表示注入到所有匹配的目标之前opcode: 无
表示注入到一个仅有一个String类型的参数且无返回类型的方法被调用之前,且传入的必须是常量字符串。
args: 允许下列参数:ldc: 这个参数必须存在,表示匹配指定传入String参数的值,例如ldc=Minecraft 1.12.2log: 同INVOKE
target: 同INVOKEordinal: 指定注入到哪个同时被target和ldc参数匹配的目标前,计数从0开始opcode: 无
表示注入到一个有返回类型的方法被调用之后,如果这个方法被调用之后的值会立即传入一个局部变量,则会在传入局部变量之后注入(也就是*STORE操作符之后)。
args: 同INVOKEtarget: 同INVOKEordinal: 指定注入到哪个被target匹配的目标后,计数从0开始。如果不指定,表示注入到所有匹配的目标之后opcode: 无
表示注入到一个字段被引用之前。
args: 允许下列参数:array: 仅当target指向一个数组元素相关操作的目标时可用,有以下取值:array=get: 匹配数组元素被引用的时候array=set: 匹配数组元素被赋值的时候array=length: 匹配数组的length属性被引用的时候
log: 同INVOKE
target: 指定一个带完整限定名的字段签名,如果不指定,则匹配所有的符合opcode的字段引用,以下写法是合法的:net.minecraft.client.Minecraft.instance:Lnet/minecraft/client/Minecraft;net/minecraft/client/Minecraft.instance:Lnet/minecraft/client/Minecraft;Lnet/minecraft/client/Minecraft;instance:Lnet/minecraft/client/Minecraft;
ordinal: 指定注入到哪个同时被args.array、target和opcode匹配的目标前,计数从0开始。如果不指定,表示注入到所有匹配的目标之前opcode: 指定匹配的操作符,如果不指定,则匹配所有的符合args.array和target的字段引用,有以下取值:178: 表示匹配GETSTATIC操作符(读取静态字段)179: 表示匹配PUTSTATIC操作符(写入静态字段)180: 表示匹配GETFIELD操作符(读取实例字段)181: 表示匹配PUTFIELD操作符(写入实例字段)
如果target和opcode都不指定,则表示匹配所有的字段引用
表示注入到NEW操作符之前。
args: 允许下列参数:class: 仅当target属性未被指定的时候使用,表示匹配指定实例化的类型(例如class=net/minecraft/client/tutorial/Tutorial)
target: 仅当args.class未被指定时可用,以下写法是合法的:net/minecraft/client/tutorial/Tutorial(Lnet/minecraft/client/Minecraft;)Lnet/minecraft/client/tutorial/Tutorial;(表示匹配使用指定构造函数实例化的目标)
ordinal: 指定注入到哪个匹配args.class或者target的目标之前,计数从0开始。如果不指定,表示注入到所有匹配的目标之前opcode: 无
以下两种写法还是有差距的:
写法A: @At(value = "NEW", target = "(Lnet/minecraft/client/Minecraft;)Lnet/minecraft/client/tutorial/Tutorial;")
写法B: @At(value = "INVOKE", target = "Lnet/minecraft/client/tutorial/Tutorial;<init>(Lnet/minecraft/client/Minecraft;)V")
写法A匹配的是NEW操作符,而写法B匹配的是INVOKESPECIAL操作符,这意味着写法A匹配的地方构造函数的参数还没有被调用,而写法B匹配的地方参数已经调用完毕,即将执行构造函数本身。
表示注入到一个常量被引用之前(即常量引用操作符之前)。
args: 允许以下参数:nullValue: 匹配null引用(填nullValue=true)intValue: 匹配int类型常量引用(例如intValue=10),对于像if (x>0)的代码,字节码中是没有引用0的操作的,请使用expandZeroConditionsfloatValue: 匹配float类型常量引用longValue: 匹配long类型常量引用doubleValue: 匹配double类型常量引用stringValue: 匹配String类型常量引用class: 匹配类名.class这样的引用,填这个类的完整限定名(例如class=net.minecraft.client.Minecraft)log: 同INVOKEexpandZeroConditions: 参考下面一节常量引用特殊情况:与0比较
target: 无ordinal: 指定注入到哪个匹配args的目标之前,计数从0开始。如果不指定,表示注入到所有匹配的目标之前opcode: 无
如果要匹配多个expandZeroConditions,请用逗号隔开,如expandZeroConditions=LESS_THAN_ZERO,GREATER_THAN_ZERO
表示注入到跳转操作符之前。
args: 无target: 无ordinal: 指定注入到哪个匹配opcode的目标之前,计数从0开始。如果不指定,表示注入到所有匹配的目标之前opcode: 指定一个匹配操作符,如果不指定,则匹配所有跳转操作符。允许以下取值:153: 表示匹配IFEQ操作符154: 表示匹配IFNE操作符155: 表示匹配IFLT操作符156: 表示匹配IFGE操作符157: 表示匹配IFGT操作符158: 表示匹配IFLE操作符159: 表示匹配IF_ICMPEQ操作符160: 表示匹配IF_ICMPNE操作符161: 表示匹配IF_ICMPLT操作符162: 表示匹配IF_ICMPGE操作符163: 表示匹配IF_ICMPGT操作符164: 表示匹配IF_ICMPLE操作符165: 表示匹配IF_ACMPEQ操作符166: 表示匹配IF_ACMPNE操作符167: 表示匹配GOTO操作符168: 表示匹配JSR操作符198: 表示匹配IFNULL操作符199: 表示匹配IFNONNULL操作符
表示注入到*LOAD操作符之前(读取一个局部变量之前),与@ModifyVariable注解配套使用。
args: 无target: 无ordinal: 指定注入到哪个*LOAD操作符之前,计数从0开始,从@ModifyVariable注解中的ordinal开始数起opcode: 无
表示注入到*STORE操作符之后(写入一个局部变量之后),与@ModifyVariable注解配套使用。
args: 无target: 无ordinal: 指定注入到哪个*STORE操作符之前,计数从0开始,从@ModifyVariable注解中的ordinal开始数起opcode: 无
常量引用特殊情况:与0比较
在Java中,对于像 if (x >= 0) 这样的代码,是不会出现ICONST_0来引用0的,而是像类似于 if (x.isGreaterThanOrEqualToZero()) 这样的引用方式。例如下面这一段代码:
public int foo(int num) {
if (num > 0)
return 11;
return 22;
}它对应的一部分字节码是:
L0
LINENUMBER 6 L0
ILOAD 1 // num 入栈
IFLE L1 // 这里直接跳转,而没有引用0
L2
LINENUMBER 7 L2
BIPUSH 11
IRETURN
L1
LINENUMBER 8 L1
FRAME SAME
BIPUSH 22
IRETURN
L3因为IFEQ、IFNE之类的操作符本身就是与0比较并跳转,为了解决这个问题,Mixin引入了Constant.Condition枚举:
LESS_THAN_ZERO: 表示<或者>=操作,用于匹配if(x<0)LESS_THAN_OR_EQUAL_TO_ZERO: 表示<=或者>操作,用于匹配if(x<=0)GREATER_THAN_OR_EQUAL_TO_ZERO: 等同于LESS_THAN_ZERO,用于匹配if(x>=0)GREATER_THAN_ZERO: 等同于LESS_THAN_OR_EQUAL_TO_ZERO,用于匹配if(x>0)
Last updated
Was this helpful?