Mixin v0.8 Release NotesRelease Date: 2020-01-11 // 3 months ago
Mixin 0.8 is something of a milestone so I'd like to take this opportunity to talk about both important changes in this version, and also draw attention to some more subtle changes addressing minor bugs and improving general quality of life for mixin authors.
🔖 Version 0.8 brings a lot of internal structural changes to Mixin but a not insignificant part of the update deals with myriad small issues which both make some Mixin features more reliable and consistent (from a mixin author point of view) and also make 0.8 feel more mature by fixing small corner-cases where functionality was either poorly documented or just plain missing. The result is that Mixin 0.8 presents a noticably more refined end-user experience which I hope will be felt by those who work with mixins on a regular basis, but also by newcomers who could often be caught out by some of Mixin's hidden gotchas.
👍 Obviously headline features for 0.8 are support for ASM 6.2 and cpw's excellent new modding platform ModLauncher. Like all Mixin versions, a focus on backwards compatibility has always been paramount, and it's with some reluctance that I've had to introduce a fairly unpleasant departure from that policy in the 0.8: namely the removal of the shaded-and-relocated ASM library in favour of using stock ASM. To be clear, this only has implications for the following elements:
- 🔌 Mixin config companion plugins which make use of the
postApplycallbacks to inspect or make changes to target classes.
- Custom injection points
In the former case, a shim is provided so that any mixins compiled for 0.7 or earlier and using a config companion plugin will continue to work if they do not make any use of the supplied
ClassNode(eg. if they use the callback purely for logging or other similar purposes). For the latter case, an error will be reported.
This change was unfortunately necessary since, under the ModLauncher regime, transformers work directly with a
ClassNodecreated by the transformation pipeline rather than with raw
bytearrays. Since mixin works extensively with the ASM tree API itself, the only alternative would be to somehow proxy the incoming ClassNode - an unacceptable performance penalty for such a small backwards compatibility benefit.
⬆️ The practical upshot of all this is that the vast majority of mods running under 1.12.x and using Mixin will continue to work if they upgrade to Mixin 0.8 in-place, or if a newer mod bundling 0.8 is present. Things will only break if a mod compiled for 0.7.x and using either of the features mentioned above is loaded under a Mixin 0.8 library bundled by another mod. As always, I have adhered to the "fail fast" contract, and Mixin will crash early with a descriptive error if this situation arises.
With ASM 6.2 comes the possibility of supporting Java 9 and partial support for Java 10. Though mixin doesn't specifically provide any functionality for those java versions yet, the compatibility levels are now available. But why - I hear you ask - ASM 6.2 when 7.0 and even 7.2 are available? The primary reason is to maintain parity with ModLauncher, since it now exists as the primary target platform for Mixin, compiling under that version makes the most sense for ensuring stability. A secondary reason is that incremental updates make the process of tracking down version-specific bugs slightly easier, since they can be identified and squashed on an ongoing basis.
👀 In order to preserve backwards compatibility with older platforms, Mixin will still run on ASM 5.x and this is achieved by detecting the ASM version at startup and gating unsupported functionality based on the detected version. This mechanism also provides a means for supporting future versions, so expect to see ASM 7.x support arrive in the not-too-distant future.
Toolchain and Environment Changes
Mixin 0.8 adds support for ForgeGradle 3.0 used for Minecraft 1.13 and 1.14 modding, specifically adding support for the new TSRG mappings format. The old services are still available to maintain backwards compatibility with 1.12 and earlier. However note that for MixinGradle, the companion gradle plugin which makes using the mixin AP much easier, you need to update to MixinGradle 0.7 in order to support ForgeGradle 3.0 and the new mappings type.
Since CoreMods have changed significantly in Forge, Mixin now provides a new bootstrapping mechanism in the form of Mixin Connectors. Mixin Connectors replace the functionality which would have previously been the duty of your CoreMod class, with the exception of initialising mixin itself. Migrating CoreMod code to Mixin Connector for 1.14 requires 2 steps:
- Create a class which implements IMixinConnector and place your initialisation code sans the call to
- Specify the name of your
IMixinConnectorclass as the value for the
MixinConnectorkey in your manifest
🛠 Notable Bug Fixes
🖨 If you're a long-time user of Mixin, then the more interesting changes are likely to be those with a smaller footprint but which nevertheless address some irritations, minor bugs and omissions with Mixin. Notable changes are as follows:
Static accessor methods now work properly
@Accessormethod in an interface would function only if the class loading order was such that the target class was loaded first. Static accessors now work correctly regardless of the class loading order
@Invokercan now be used to invoke private constructors
It's my life mission to make Access Transformers redundant and a new feature in Mixin 0.8 is the ability to create an
@Invokermethod which acts as a factory, invoking an inaccessible constructor.
Accessor mixins now work properly inside other mixins
A small bug with a big impact. Accessor Mixins now work properly when used inside another mixin's code, previously attempting to do this would yield a slightly cryptic error about not finding the accessor mixin in the target's hierarchy.
Mixin application continues if an optional mixin fails
⚠ Previously any mixin failing during preprocessing or application would lead the mixin applicator shutting down, with optional mixins generating a warning and required mixins raising an exception. From 0.8, failures in optional mixins are collected and reported, but other mixins for the same target will still be applied. The behaviour for required mixins remains unchanged.
More reliable local variable capture in
Local variable capture via callback injector has always been somewhat brittle due to the fickle nature of the LVT and the knock-on effects this has had when passing obfuscated classes through ASM. Previously the local variable detection in Mixin relied heavily on ASM's
EXPAND_FRAMESand a combination of heuristics and analysis to walk through a target method to determine available locals at the injection point.
EXPAND_FRAMESis not applied by ModLauncher, it was necessary to improve the local variable detection to handle compressed frames. This has had a side-effect of producing more reliable local variable tables and also revealed some minor bugs in the analyser code which, although unlikely to have had any detectable effect in the real world, nonetheless represent an improvement of the system as a whole.
👌 Improvements and New Features
Injectors with no targets are now allowed to fail
The introduction of the
requireexpressions on injectors made it possible for Mixin to determine if failure to inject was an error condition or not. Since injectors might fail because code is changed by another transformer this allowed injectors some degree of lattitude to fail quietly if they weren't important. However an injector which matched no targets was always treated as an error condition. This was problematic when, for example, an extra method might be added by another transformer, since the failing injector would lead to a crash when the mod was not present. From 0.8 onwards, the
requireare now honoured for zero-target injectors.
SuppressWarningsis now supported by the Annotation Processor
📦 It is now possible to use the regular java
@SuppressWarningsannotation to silence AP warnings which you don't care about. I will add a wiki page with the available options but for now you can check out the source for SuppressedBy which lists the available options. As you'd expect, the warning suppression is available at the element, class or package levels as required.
👌 Improvements to
@Coercefor injector arguments
👌 Support for the
@Coerceannotation has always been a bit inconsistent. Mixin 0.8 overhauls support for
@Coerceto add three main improvements:
- Can now be used to coerce not only to superclasses, but also interfaces including mixed-in interfaces such as acccessors
- Now works on field, array and constructor redirectors as well as the previously-supported invoke redirectors
- Can now be used on all redirector and modifyconstant arguments, including captured target arguments and the method return types (to coerce return type, annotate the handler method itself)
@ModifyVariableinjectors can now capture target arguments
Like its related injectors
@ModifyVariableinjector is now able to capture arguments from the injector target.
@Coercecan be used on these arguments in the same way as the other injectors too.
Increased detail in applicator and pre-processor errors
Errors raised by Mixin are generally good at pinpointing where a problem has occurred. However with unexpected exceptions it's usually easy to find where an error occurred but not necessarily why. The mixin preprocessor and applicator now track their work internally in a call-stack-like manner and decorate unexpected errors when they occur. This makes it possible, for example, to see which particular opcode the preprocessor has choked on, or which method it was handling when it did so. The aim is to make internal bugs easier to identify when they are triggered by malformed bytecode or a failure of internal state.
🔄 Changes to Mixin Services
👍 Internally, significant changes have been made to the mixin pipeline and the structure of mixin services to both add for support ModLauncher and make it easier to implement mixin services in general:
Mixin services now share a common base class
Creating the ModLauncher service helped identify features of the service which could be shared, and these are now provided in a common base class
MixinServiceAbstract. This helps implementors of mixin services in two ways: firstly removing the need to copy a tonne of boilerplate from other services; but secondly making the actual service implementations much cleaner, separating the service-specific duties from the boilerplate.
Mixin services are now more modular
➕ Adding to the original roster of modules
IClassBytecodeProvider, other service duties have been delegated to (now optional) secondary service modules:
ITransformerProviderprovides interaction with transformers, if present. Now entirely optional so that simple environments not leveraging transforming classloaders do not need to supply boilerplate for this functionality.
IClassTrackerprovides class load and transform tracking supporting the previous functionality of detecting premature class loads and reporting on class load restrictions.
IMixinAuditTrailreceives audit callbacks for primary mixin transformer actions such as applying a mixin to a class and post-processing of classes. It is up to the service to determine how to sink these callbacks, in the ModLauncher services they are forwarded to the classloader's internal audit trail
📚 There's still a backlog of issues on the GitHub Issues page which I will be tackling over the course of the coming months, but the issues do not represent an exhaustive list of planned changes for Mixin. As well as taking some time to move the documentation forward, a selection of new features are in the pipeline for release in the near future so watch this space for updates.
- 🔌 Mixin config companion plugins which make use of the