[Mixin] Conflicting synthetic bridge target method name

Hi all,

I have the following mixin:

@Mixin(EntityMooshroom.class)
public abstract class MixinEntityMooshroom extends EntityCow {

	public MixinEntityMooshroom(World world) {
		super(world);
	}

	@Inject(method = "processInteract", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/passive/EntityMooshroom;setDead()V"))
	//@Inject(method = "processInteract", at = @At("HEAD"))
	public void onProcessInteract(EntityPlayer player, EnumHand hand, CallbackInfoReturnable<Boolean> ci) {
		if (world.isRemote) {
			EnchantmentCracker.toolDamageCheck(player.getHeldItem(hand), 1);
		}
	}

}

Inside the IDE, this mixin is applied without issues. For me, it also applies without issues outside the IDE, but multiple users have reported to me the following stacktrace:

[16:58:08] [main/FATAL]: Mixin apply failed mixins.enchantment_cracking.json:MixinEntityMooshroom -> zy: org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException Conflicting synthetic bridge target method name in synthetic bridge method a(Lvb;)Lvb; Existing:c Incoming:b
org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException: Conflicting synthetic bridge target method name in synthetic bridge method a(Lvb;)Lvb; Existing:c Incoming:b
	at org.spongepowered.asm.mixin.transformer.MixinPreProcessorStandard.attachUniqueMethod(MixinPreProcessorStandard.java:499) ~[liteloader-1.12-SNAPSHOT.jar:1.12-SNAPSHOT+jnks-b9.git-4ca9c4eaf0528ac1e2967a31de7e0697700965cf]
	at org.spongepowered.asm.mixin.transformer.MixinPreProcessorStandard.attachMethods(MixinPreProcessorStandard.java:300) ~[liteloader-1.12-SNAPSHOT.jar:1.12-SNAPSHOT+jnks-b9.git-4ca9c4eaf0528ac1e2967a31de7e0697700965cf]
	at org.spongepowered.asm.mixin.transformer.MixinPreProcessorStandard.attach(MixinPreProcessorStandard.java:257) ~[liteloader-1.12-SNAPSHOT.jar:1.12-SNAPSHOT+jnks-b9.git-4ca9c4eaf0528ac1e2967a31de7e0697700965cf]
	at org.spongepowered.asm.mixin.transformer.MixinPreProcessorStandard.createContextFor(MixinPreProcessorStandard.java:237) ~[liteloader-1.12-SNAPSHOT.jar:1.12-SNAPSHOT+jnks-b9.git-4ca9c4eaf0528ac1e2967a31de7e0697700965cf]
	at org.spongepowered.asm.mixin.transformer.MixinInfo.createContextFor(MixinInfo.java:1144) ~[liteloader-1.12-SNAPSHOT.jar:1.12-SNAPSHOT+jnks-b9.git-4ca9c4eaf0528ac1e2967a31de7e0697700965cf]
	at org.spongepowered.asm.mixin.transformer.MixinApplicatorStandard.apply(MixinApplicatorStandard.java:254) ~[liteloader-1.12-SNAPSHOT.jar:1.12-SNAPSHOT+jnks-b9.git-4ca9c4eaf0528ac1e2967a31de7e0697700965cf]
	at org.spongepowered.asm.mixin.transformer.TargetClassContext.applyMixins(TargetClassContext.java:353) ~[liteloader-1.12-SNAPSHOT.jar:1.12-SNAPSHOT+jnks-b9.git-4ca9c4eaf0528ac1e2967a31de7e0697700965cf]
	at org.spongepowered.asm.mixin.transformer.MixinTransformer.apply(MixinTransformer.java:724) [liteloader-1.12-SNAPSHOT.jar:1.12-SNAPSHOT+jnks-b9.git-4ca9c4eaf0528ac1e2967a31de7e0697700965cf]
	at org.spongepowered.asm.mixin.transformer.MixinTransformer.applyMixins(MixinTransformer.java:703) [liteloader-1.12-SNAPSHOT.jar:1.12-SNAPSHOT+jnks-b9.git-4ca9c4eaf0528ac1e2967a31de7e0697700965cf]
	at org.spongepowered.asm.mixin.transformer.MixinTransformer.transformClassBytes(MixinTransformer.java:509) [liteloader-1.12-SNAPSHOT.jar:1.12-SNAPSHOT+jnks-b9.git-4ca9c4eaf0528ac1e2967a31de7e0697700965cf]
	at org.spongepowered.asm.mixin.transformer.Proxy.transform(Proxy.java:72) [liteloader-1.12-SNAPSHOT.jar:1.12-SNAPSHOT+jnks-b9.git-4ca9c4eaf0528ac1e2967a31de7e0697700965cf]
	at net.minecraft.launchwrapper.LaunchClassLoader.runTransformers(LaunchClassLoader.java:279) [launchwrapper-1.12.jar:?]
	at net.minecraft.launchwrapper.LaunchClassLoader.findClass(LaunchClassLoader.java:176) [launchwrapper-1.12.jar:?]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424) [?:1.8.0_51]
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357) [?:1.8.0_51]
	at vg.c(SourceFile:293) [vg.class:?]
	at ng.c(SourceFile:479) [ng.class:?]
	at bhz.<init>(SourceFile:394) [bhz.class:?]
	at net.minecraft.client.main.Main.main(SourceFile:123) [Main.class:?]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_51]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_51]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_51]
	at java.lang.reflect.Method.invoke(Method.java:497) ~[?:1.8.0_51]
	at net.minecraft.launchwrapper.Launch.launch(Launch.java:135) [launchwrapper-1.12.jar:?]
	at net.minecraft.launchwrapper.Launch.main(Launch.java:28) [launchwrapper-1.12.jar:?]

The full mod source code is available at https://github.com/Earthcomputer/EnchantmentCracking

Any ideas as to what’s going on?

(As a side note, this mixin also fails alongside Forge, but that’s an unrelated issue and I know what the problem is)

This isn’t actually your fault, it’s a problem with the way Java allows covariance of return types for derived methods. As to why it’s happening when the bridge target is absent, I don’t know. The only explanations I can see are:

  • The java compiler you’re using is creating the synthetic bridge for some reason under some circumstances
  • The version of the code on your repo is not the version that’s actually causing the issue.

The bridge in question is (I think) the createChild method. Have you by any chance been attempting to provide an override for that method in your mixin?

There are ways around this issue, but discovering whether the root cause is the first or second above is the key to which way you solve it.


Some explanation, in case you’re wondering what a synthetic bridge actually is:

Synthetic is a simple enough term which simply means something which ends up in the compiled class which the programmer didn’t write, the compiler did. It’s some piece of code which supports syntactic sugar in the language that the runtime actually does not natively support for example.

In this case, the return type of the createChild method in EntityCow is covariant over the parent class’s return type, in that it returns EntityCow in place of the parent class version which returns EntityAgeable. The problem here is that from a conceptual point of view the EntityCow method overrrides the parent version, from the runtime’s perspective it does not, because the return type is different.

In order for this to work, the compiler creates a “bridge method” which does override the parent method, and that bridge simply calls the method with the covariant return type. Effectively the bridge method looks like this:

public EntityAgeable createChild(EntityAgeable ageable)
{
    // Call to createChild that returns EntityCow
    return this.createChild(this.world);
}

Of course in Java that looks recursive but the call is actually to the new, specialised createChild method and the bridge method is invisible to you as a developer.

In Mixin, bridge methods are allowed to overlap, so that if you have a bridge method which is functionally identical to another, incumbent bridge method, everything is fine. However conflicting bridge methods cannot be allowed to exist because one will always be “wrong”.

You can fix these situations by preventing the cause of the bridge (eg. avoiding covariant returns or contravariant parameters) or by providing a non-synthetic bridge yourself which handles the delegation, eg. by using CHECKCAST.

Before determining the solution however, it’s vital to know whether a bridge method is being created by the compiler for some arcane reason (this can be easily determined by disassembling the mixin code in your jar) or whether it’s some userland code you forgot about.

2 Likes

There is no binary version of my mod available yet, so the users in question compiled the mod from source themselves. I’ve asked them to send me their compiled copies to help figure out if it’s the first issue.
I somewhat doubt it’s the second issue.
I haven’t done anything to the createChild method.

Because of the nature of bridge methods, it’s entirely possible that the compiler on their machines is “playing it safe” by generating bridge methods in derived classes (as a possible measure toward proactively creating more robust binary compatibility.

Since this is the case, you need to either:

  • Obtain the compiled binaries from the other users, analyse the conflict and then provide a concrete (user-defined) version of the bridge method.

  • Avoid creation of the bridge method by decoupling the mixin from the superclass, use supermixins where shadowing of superclass members is required.

Neither is really ideal but we’re working around limitations of the Java compiler here unfortunately.

I obtained a binary from one of the users and their MixinEntityMooshroom.class did indeed contain the extra bridge method, while mine doesn’t.
I think in this scenario the second option is most appropriate, since the only reference to a field in the superclass is a publc field, and I can just do ((Entity) (Object) this).world.