From 3fdbb607294b5fd0ddc6985d8dda299d0f09fba9 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Thu, 6 Aug 2015 17:27:51 -0700 Subject: [PATCH] References into repr(packed) structs should be `unsafe`. --- text/0000-repr-packed-unsafe-ref.md | 438 ++++++++++++++++++++++++++++ 1 file changed, 438 insertions(+) create mode 100644 text/0000-repr-packed-unsafe-ref.md diff --git a/text/0000-repr-packed-unsafe-ref.md b/text/0000-repr-packed-unsafe-ref.md new file mode 100644 index 00000000000..473c7a991e1 --- /dev/null +++ b/text/0000-repr-packed-unsafe-ref.md @@ -0,0 +1,438 @@ +- Feature Name: NA +- Start Date: 2015-08-06 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary + +Taking a reference into a struct marked `repr(packed)` should become +`unsafe`, because it can lead to undefined behaviour. `repr(packed)` +structs need to be banned from storing `Drop` types for this reason. + +# Motivation + +Issue [#27060](https://github.com/rust-lang/rust/issues/27060) noticed +that it was possible to trigger undefined behaviour in safe code via +`repr(packed)`, by creating references `&T` which don't satisfy the +expected alignment requirements for `T`. + +Concretely, the compiler assumes that any reference (or raw pointer, +in fact) will be aligned to at least `align_of::()`, i.e. the +following snippet should run successfully: + +```rust +let some_reference: &T = /* arbitrary code */; + +let actual_address = some_reference as *const _ as usize; +let align = std::mem::align_of::(); + +assert_eq!(actual_address % align, 0); +``` + +However, `repr(packed)` allows on to violate this, by creating values +of arbitrary types that are stored at "random" byte addresses, by +removing the padding normally inserted to maintain alignment in +`struct`s. E.g. suppose there's a struct `Foo` defined like +`#[repr(packed, C)] struct Foo { x: u8, y: u32 }`, and there's an +instance of `Foo` allocated at a 0x1000, the `u32` will be placed at +`0x1001`, which isn't 4-byte aligned (the alignment of `u32`). + +Issue #27060 has a snippet which crashes at runtime on at least two +x86-64 CPUs (the author's and the one playpen runs on) and almost +certainly most other platforms. + +```rust +#![feature(simd, test)] + +extern crate test; + +// simd types require high alignment or the CPU faults +#[simd] +#[derive(Debug, Copy, Clone)] +struct f32x4(f32, f32, f32, f32); + +#[repr(packed)] +#[derive(Copy, Clone)] +struct Unalign(T); + +struct Breakit { + x: u8, + y: Unalign +} + +fn main() { + let val = Breakit { x: 0, y: Unalign(f32x4(0.0, 0.0, 0.0, 0.0)) }; + + test::black_box(&val); + + println!("before"); + + let ok = val.y; + test::black_box(ok.0); + + println!("middle"); + + let bad = val.y.0; + test::black_box(bad); + + println!("after"); +} +``` + +On playpen, it prints: + +``` +before +middle +playpen: application terminated abnormally with signal 4 (Illegal instruction) +``` + +That is, the `bad` variable is causing the CPU to fault. The `let` +statement is (in pseudo-Rust) behaving like `let bad = +load_with_alignment(&val.y.0, align_of::());`, but the +alignment isn't satisfied. (The `ok` line is compiled to a `movupd` +instruction, while the `bad` is compiled to a `movapd`: `u` == +unaligned, `a` == aligned.) + +(NB. The use of SIMD types in the example is just to be able to +demonstrate the problem on x86. That platform is generally fairly +relaxed about pointer alignments and so SIMD & its specialised `mov` +instructions are the easiest way to demonstrate the violated +assumptions at runtime. Other platforms may fault on other types.) + +Being able to assume that accesses are aligned is useful, for +performance, and almost all references will be correctly aligned +anyway (`repr(packed)` types and internal references into them are +quite rare). + +The problems with unaligned accesses can be avoided by ensuring that +the accesses are actually aligned (e.g. via runtime checks, or other +external constraints the compiler cannot understand directly). For +example, consider the following + +```rust +#[repr(packed, C)] +struct Bar { + x: u8, + y: u16, + z: u8, + w: u32, +} +``` + +Taking a reference to some of those fields may cause undefined +behaviour, but not always. It is always correct to take +a reference to `x` or `z` since `u8` has alignment 1. If the struct +value itself is 4-byte aligned (which is not guaranteed), `w` will +also be 4-byte aligned since the `u8, u16, u8` take up 4 bytes, hence +it is correct to take a reference to `w` in this case (and only that +case). Similarly, it is only correct to take a reference to `y` if the +struct is at an odd address, so that the `u16` starts at an even one +(i.e. is 2-byte aligned). + +# Detailed design + +It is `unsafe` to take a reference to the field of a `repr(packed)` +struct. It is still possible, but it is up to the programmer to ensure +that the alignment requirements are satisfied. Referencing +(by-reference, or by-value) a subfield of a struct (including indexing +elements of a fixed-length array) stored inside a `repr(packed)` +struct counts as taking a reference to the `packed` field and hence is +unsafe. + +It is still legal to manipulate the fields of a `packed` struct by +value, e.g. the following is correct (and not `unsafe`), no matter the +alignment of `bar`: + +```rust +let bar: Bar = ...; + +let x = bar.y; +bar.w = 10; +``` + +It is illegal to store a type `T` implementing `Drop` (including a +generic type) in a `repr(packed)` type, since the destructor of `T` is +passed a reference to that `T`. The crater run (see appendix) found no +crate that needs to use `repr(packed)` to store a `Drop` type (or a +generic type). The generic type rule is conservatively approximated by +disallowing generic `repr(packed)` structs altogether, but this can be +relaxed (see Alternatives). + +Concretely, this RFC is proposing the introduction of the `// error`s +in the following code. + +```rust +struct Baz { + x: u8, +} + +#[repr(packed)] +struct Qux { // error: generic repr(packed) struct + y: Baz, + z: u8, + w: String, // error: storing a Drop type in a repr(packed) struct + t: [u8; 4], +} + +let mut qux = Qux { ... }; + +// all ok: +let y_val = qux.y; +let z_val = qux.z; +let t_val = qux.t; +qux.y = Baz { ... }; +qux.z = 10; +qux.t = [0, 1, 2, 3]; + +// new errors: + +let y_ref = &qux.y; // error: taking a reference to a field of a repr(packed) struct is unsafe +let z_ref = &mut qux.z; // ditto +let y_ptr: *const _ = &qux.y; // ditto +let z_ptr: *mut _ = &mut qux.z; // ditto + +let x_val = qux.y.x; // error: directly using a subfield of a field of a repr(packed) struct is unsafe +let x_ref = &qux.y.x; // ditto +qux.y.x = 10; // ditto + +let t_val = qux.t[0]; // error: directly indexing an array in a field of a repr(packed) struct is unsafe +let t_ref = &qux.t[0]; // ditto +qux.t[0] = 10; // ditto +``` + +(NB. the subfield and indexing cases can be resolved by first copying +the packed field's value onto the stack, and then accessing the +desired value.) + +## Staging + +This change will first land as warnings indicating that code will be +broken, with the warnings switched to the intended errors after one +release cycle. + +# Drawbacks + +This will cause some functionality to stop working in +possibly-surprising ways (NB. the drawback here is mainly the +"possibly-surprising", since the functionality is broken with general +`packed` types.). For example, `#[derive]` usually takes references to +the fields of structs, and so `#[derive(Clone)]` will generate +errors. However, this use of derive is incorrect in general (no +guarantee that the fields are aligned), and, one can easily replace it +by: + +```rust +#[derive(Copy)] +#[repr(packed)] +struct Foo { ... } + +impl Clone for Foo { fn clone(&self) -> Foo { *self } } +``` + +Similarly, `println!("{}", foo.bar)` will be an error despite there +not being a visible reference (`println!` takes one internally), +however, this can be resolved by, for instance, assigning to a +temporary. + +# Alternatives + +- A short-term solution would be to feature gate `repr(packed)` while + the kinks are worked out of it +- Taking an internal reference could be made flat-out illegal, and the + times when it is correct simulated by manual raw-pointer + manipulation. +- The rules could be made less conservative in several cases, however + the crater run didn't indicate any need for this: + - a generic `repr(packed)` struct can use the generic in ways that + avoids problems with `Drop`, e.g. if the generic is bounded by + `Copy`, or if the type is only used in ways that are `Copy` such + as behind a `*const T`. + - using a subfield of a field of a `repr(packed)` struct by-value + could be OK. + +# Unresolved questions + +None. + +# Appendix + +## Crater analysis + +Crater was run on 2015/07/23 with a patch that feature gated `repr(packed)`. + +High-level summary: + +- several unnecessary uses of `repr(packed)` (patches have been + submitted and merged to remove all of these) +- most necessary ones are to match the declaration of a struct in C +- many "necessary" uses can be replaced by byte arrays/arrays of smaller types +- 8 crates are currently on stable themselves (unsure about deps), 4 are already on nightly + - 1 of the 8, http2parse, is essentially only used by a nightly-only crate (tendril) + - 4 of the stable and 1 of the nightly crates don't need `repr(packed)` at all + +| | stable | needed | FFI only | +|------------|--------|--------|----------| +| image | ✓ | | | +| nix | ✓ | ✓ | ✓ | +| tendril | | ✓ | | +| assimp-sys | ✓ | ✓ | ✓ | +| stemmer | ✓ | | | +| x86 | ✓ | ✓ | ✓ | +| http2parse | ✓ | ✓ | | +| nl80211rs | ✓ | ✓ | ✓ | +| openal | ✓ | | | +| elfloader | | ✓ | ✓ | +| x11 | ✓ | | | +| kiss3d | ✓ | | | + +More detailed analysis inline with broken crates. (Don't miss `kiss3d` in the non-root section.) + +### Regression report c85ba3e9cb4620c6ec8273a34cce6707e91778cb vs. 7a265c6d1280932ba1b881f31f04b03b20c258e5 + +* From: c85ba3e9cb4620c6ec8273a34cce6707e91778cb +* To: 7a265c6d1280932ba1b881f31f04b03b20c258e5 + +#### Coverage + +* 2617 crates tested: 1404 working / 1151 broken / 40 regressed / 0 fixed / 22 unknown. + +#### Regressions + +* There are 11 root regressions +* There are 40 regressions + +#### Root regressions, sorted by rank: + +* [image-0.3.11](https://crates.io/crates/image) + ([before](https://tools.taskcluster.net/task-inspector/#V6QBA9LfTT6mhFJ0Yo7nJg)) + ([after](https://tools.taskcluster.net/task-inspector/#QU9d4XEPSWOg7CIGFpATDg)) + - [use](https://github.com/PistonDevelopers/image/blob/8e64e0d78e465ddfa13cd6627dede5fd258386f6/src/tga/decoder.rs#L75) + seems entirely unnecessary (no raw bytewise operations on the + struct itself) + + On stable. +* [nix-0.3.9](https://crates.io/crates/nix) + ([before](https://tools.taskcluster.net/task-inspector/#X3HMXrq4S_GMNbeeAY8i6w)) + ([after](https://tools.taskcluster.net/task-inspector/#kz0vDaAhRRuKww2l-FvYpQ)) + - [use](https://github.com/carllerche/nix-rust/blob/5801318c0c4c6eeb3431144a89496830f55d6628/src/sys/epoll.rs#L98) + required to match + [C struct](https://github.com/torvalds/linux/blob/de182468d1bb726198abaab315820542425270b7/include/uapi/linux/eventpoll.h#L53-L62) + + On stable. +* [tendril-0.1.2](https://crates.io/crates/tendril) + ([before](https://tools.taskcluster.net/task-inspector/#zQH7ShADR5O9eQe1mg3e6A)) + ([after](https://tools.taskcluster.net/task-inspector/#zI-PoIZHTm-7Urq3CLsXeg)) + - [use 1](https://github.com/servo/tendril/blob/faf97ded26213e561f8ad2768113cc05b6424748/src/buf32.rs#L19) + not strictly necessary? + - [use 2](https://github.com/servo/tendril/blob/faf97ded26213e561f8ad2768113cc05b6424748/src/tendril.rs#L43) + required on 64-bit platforms to get size_of::<Header>() == 12 rather + than 16. + - [use 3](https://github.com/servo/tendril/blob/faf97ded26213e561f8ad2768113cc05b6424748/src/tendril.rs#L91), + as above, does some precise tricks with the layout for optimisation. + + Requires nightly. +* [assimp-sys-0.0.3](https://crates.io/crates/assimp-sys) ([before](https://tools.taskcluster.net/task-inspector/#rTrUh0VQR2uWXMQw14kRIA)) ([after](https://tools.taskcluster.net/task-inspector/#AR36o35FRV-mVInHKWFDrg)) + - [many uses](https://github.com/Eljay/assimp-sys/search?utf8=%E2%9C%93&q=packed), + required to match + [C structs](https://github.com/assimp/assimp/blob/f3d418a199cfb7864c826665016e11c65ddd7aa9/include/assimp/types.h#L227) + (one example). In author's words: + + > [11:36:15] <eljay> huon: well my assimp binding is basically abandoned for now if you are just worried about breaking things, and seems unlikely anyone is using it :P + + On stable. +* [stemmer-0.1.1](https://crates.io/crates/stemmer) ([before](https://tools.taskcluster.net/task-inspector/#0Affr5PrTnGoBukeRwuiKw)) ([after](https://tools.taskcluster.net/task-inspector/#8xGRmPxOQS2NHbvgXMvmWQ)) + - [use](https://github.com/lady-segfault/stemmer-rs/blob/4090dcf7a258df5031c10754c8de118e0ca93512/src/stemmer.rs#L7), completely unnecessary + + On stable. +* [x86-0.2.0](https://crates.io/crates/x86) ([before](https://tools.taskcluster.net/task-inspector/#__VYVs6QSYm4JF68fSXibw)) ([after](https://tools.taskcluster.net/task-inspector/#xj8paeiaR0OGkK1v2raHYg)) + - [several similar uses](https://github.com/gz/rust-x86/search?utf8=%E2%9C%93&q=packed), + specific layout necessary for raw interaction with CPU features + + Requires nightly. +* [http2parse-0.0.3](https://crates.io/crates/http2parse) ([before](https://tools.taskcluster.net/task-inspector/#CUr_5dfgQMywZmG_ER7ZGQ)) ([after](https://tools.taskcluster.net/task-inspector/#rQO3m_8iQQapN2l-PvGrRw)) + - [use](https://github.com/reem/rust-http2parse/blob/b363139ac2f81fa25db504a9256face9f8c799b6/src/payload.rs#L206), + used to get super-fast "parsing" of headers, by transmuting + `&[u8]` to `&[Setting]`. + + On stable, however: + + ```irc + [11:30:38] reem: why is https://github.com/reem/rust-http2parse/blob/b363139ac2f81fa25db504a9256face9f8c799b6/src/payload.rs#L208 packed? + [11:31:59] huon: I transmute from & [u8] to & [Setting] + [11:32:35] So repr packed gets me the layout I need + [11:32:47] With no padding between the u8 and u16 + [11:33:11] and between Settings + [11:33:17] ok + [11:33:22] (stop doing bad things :P ) + [11:34:00] (there's some problems with repr(packed) https://github.com/rust-lang/rust/issues/27060 and we may be feature gating it) + [11:35:02] reem: wait, aren't there endianness problems? + [11:36:16] Ah yes, looks like I forgot to finish the Setting interface + [11:36:27] The identifier and value methods take care of converting to types values + [11:36:39] The goal is just to avoid copying the whole buffer and requiring an allocation + [11:37:01] Right now the whole parser takes like 9 ns to parse a frame + [11:39:11] would you be sunk if repr(packed) was feature gated? + [11:40:17] or, is maybe something like `struct SettingsRaw { identifier: [u8; 2], value: [u8; 4] }` OK (possibly with conversion functions etc.)? + [11:40:46] Yea, I could get around it if I needed to + [11:40:58] Anyway the primary consumer is transfer and I'm running on nightly there + [11:41:05] So it doesn't matter too much + ``` + +* [nl80211rs-0.1.0](https://crates.io/crates/nl80211rs) ([before](https://tools.taskcluster.net/task-inspector/#rhEG57vQQHWiVCcS3kIWrA)) ([after](https://tools.taskcluster.net/task-inspector/#s97ED8oXQ4WN-Pbm3ZsFJQ)) + - [three similar uses](https://github.com/carrotsrc/nl80211rs/search?utf8=%E2%9C%93&q=packed) + to match + [C struct](http://lxr.free-electrons.com/source/include/uapi/linux/nl80211.h#L2288). + + On stable. +* [openal-0.2.1](https://crates.io/crates/openal) ([before](https://tools.taskcluster.net/task-inspector/#XUvl-638T82xgGwkrxpz5g)) ([after](https://tools.taskcluster.net/task-inspector/#Oc9wEFpbQM2Tja9sv0qt4g)) + - [several similar uses](https://github.com/meh/rust-openal/blob/9e35fd284f25da7fe90a8307de85a6ec6d392ea1/src/util.rs#L6), + probably unnecessary, just need the struct to behave like + `[f32; 3]`: pointers to it + [are passed](https://github.com/meh/rust-openal/blob/9e35fd284f25da7fe90a8307de85a6ec6d392ea1/src/listener/listener.rs#L204-L205) + to [functions expecting `*mut f32`](https://github.com/meh/rust-openal-sys/blob/master/src/al.rs#L146) pointers. + + On stable. +* [elfloader-0.0.1](https://crates.io/crates/elfloader) ([before](https://tools.taskcluster.net/task-inspector/#ssE4lk0xR3q1qYZBXK24aA)) ([after](https://tools.taskcluster.net/task-inspector/#SAH7AAVIToKkhf7QRK4C1g)) + - [two similar uses](https://github.com/gz/rust-elfloader/blob/d61db7c83d66ce65da92aed5e33a4baf35f4c1e7/src/elf.rs#L362), + required to match file headers/formats exactly. + + Requires nightly. +* [x11cap-0.1.0](https://crates.io/crates/x11cap) ([before](https://tools.taskcluster.net/task-inspector/#7wn8cjqXSOaZfpekKRY-yw)) ([after](https://tools.taskcluster.net/task-inspector/#bA6LwPreTMa8R_zYNt8Z3w)) + - [use](https://github.com/bryal/X11Cap/blob/d11b7170e6fa7c1ab370c69887b9ce71a542335d/src/lib.rs#L41) unnecessary. + + Requires nightly. + +#### Non-root regressions, sorted by rank: + +* [glium-0.8.0](https://crates.io/crates/glium) ([before](https://tools.taskcluster.net/task-inspector/#m5yEIEu-QEeM_2t4_11Opg)) ([after](https://tools.taskcluster.net/task-inspector/#Wztxoh9SQ-GqA4F3inaR9Q)) +* [mio-0.4.1](https://crates.io/crates/mio) ([before](https://tools.taskcluster.net/task-inspector/#RtT-HmwbTYuG0djpAkVLvA)) ([after](https://tools.taskcluster.net/task-inspector/#Lx1d3ukPSGyRIwIDt_w0gw)) +* [piston_window-0.11.0](https://crates.io/crates/piston_window) ([before](https://tools.taskcluster.net/task-inspector/#QE421inlRgShgoXKcUkEEA)) ([after](https://tools.taskcluster.net/task-inspector/#wIKQPW_7TjmrztHQ4Kk3hw)) +* [piston2d-gfx_graphics-0.4.0](https://crates.io/crates/piston2d-gfx_graphics) ([before](https://tools.taskcluster.net/task-inspector/#hIUDm8m6QrCdOpSF30aPjQ)) ([after](https://tools.taskcluster.net/task-inspector/#HOw14MCoQxGj7GjYIy-Lng)) +* [piston-gfx_texture-0.2.0](https://crates.io/crates/piston-gfx_texture) ([before](https://tools.taskcluster.net/task-inspector/#om-wlRW-Tm65MTlrpa8u7Q)) ([after](https://tools.taskcluster.net/task-inspector/#m9e9Vx58RA6KhCljujzzMQ)) +* [piston2d-glium_graphics-0.3.0](https://crates.io/crates/piston2d-glium_graphics) ([before](https://tools.taskcluster.net/task-inspector/#vHeYcL2gRT2aIz9JeksAfw)) ([after](https://tools.taskcluster.net/task-inspector/#yEKBSm1BQ_C0O-4GKhQgUQ)) +* [html5ever-0.2.0](https://crates.io/crates/html5ever) ([before](https://tools.taskcluster.net/task-inspector/#C0yCazihTWa4x2GxCUxasQ)) ([after](https://tools.taskcluster.net/task-inspector/#Vbl4HjqcQlq4-sJ2m1yBnQ)) +* [caribon-0.6.2](https://crates.io/crates/caribon) ([before](https://tools.taskcluster.net/task-inspector/#AJZzG5gLSY-WVMKc-MoV5w)) ([after](https://tools.taskcluster.net/task-inspector/#ornLa3ZaSC-Zbz7ICg33Tg)) +* [gj-0.0.2](https://crates.io/crates/gj) ([before](https://tools.taskcluster.net/task-inspector/#xhaiB76FQAKCEsmBkQtp1A)) ([after](https://tools.taskcluster.net/task-inspector/#rBJke3wpQqaq7wmEiQtLJA)) +* [glium_text-0.5.0](https://crates.io/crates/glium_text) ([before](https://tools.taskcluster.net/task-inspector/#IMdXVtTYSIaDrCRQ6SbLTA)) ([after](https://tools.taskcluster.net/task-inspector/#t322h_mzQGarVmsf5MHqKA)) +* [glyph_packer-0.0.0](https://crates.io/crates/glyph_packer) ([before](https://tools.taskcluster.net/task-inspector/#JmIVzau8RyOhnlTvdsRIHQ)) ([after](https://tools.taskcluster.net/task-inspector/#7k9GF09SQPya4ZrLuR6cJw)) +* [html5ever_dom_sink-0.2.0](https://crates.io/crates/html5ever_dom_sink) ([before](https://tools.taskcluster.net/task-inspector/#7GJmaAYKS9WNqnbCx5XMrw)) ([after](https://tools.taskcluster.net/task-inspector/#pHotnKLkTAqK4-LP-n2MUQ)) +* [identicon-0.1.0](https://crates.io/crates/identicon) ([before](https://tools.taskcluster.net/task-inspector/#15nnASVgStmrwqdCS1q8Rg)) ([after](https://tools.taskcluster.net/task-inspector/#WgJb_jEMQIebNgb_D2uq7Q)) +* [assimp-0.0.4](https://crates.io/crates/assimp) ([before](https://tools.taskcluster.net/task-inspector/#-i-FYpJ2Rz-bcmxGVmxoOQ)) ([after](https://tools.taskcluster.net/task-inspector/#HXR8V8NeRMyOxF0Nnhdl0w)) +* [jamkit-0.2.4](https://crates.io/crates/jamkit) ([before](https://tools.taskcluster.net/task-inspector/#mcpl8Z62Td-DFfoi9AqRnw)) ([after](https://tools.taskcluster.net/task-inspector/#XGOIXxqpRbCMy5bZ42GV5w)) +* [coap-0.1.0](https://crates.io/crates/coap) ([before](https://tools.taskcluster.net/task-inspector/#SI137HlpRsSuQrlhxlRHpQ)) ([after](https://tools.taskcluster.net/task-inspector/#dT3pt46pQtmy3CvIaC_71Q)) +* [kiss3d-0.1.2](https://crates.io/crates/kiss3d) ([before](https://tools.taskcluster.net/task-inspector/#2Bbro6uZQQCudv2ClalFTw)) ([after](https://tools.taskcluster.net/task-inspector/#9vRbugDKTDm94fjw6BcS6A)) + - [use](https://github.com/sebcrozet/kiss3d/blob/1c1d39d5f8a428609b2f7809c7237e8853ac24e9/src/text/glyph.rs#L7) seems to be unnecessary: semantically useless, just a space "optimisation", which actually makes no difference because the Vec field will be appropriately aligned always. + + On stable. +* [compass-sprite-0.0.3](https://crates.io/crates/compass-sprite) ([before](https://tools.taskcluster.net/task-inspector/#dTcfDsk1QYKWtK7EH5gnwg)) ([after](https://tools.taskcluster.net/task-inspector/#rElhdv9GS8-Zi14LSL-6Ng)) +* [dcpu16-gui-0.0.3](https://crates.io/crates/dcpu16-gui) ([before](https://tools.taskcluster.net/task-inspector/#mtbOQfFUTDiZcMUc65LD3w)) ([after](https://tools.taskcluster.net/task-inspector/#co31ZVgNQ1mYyDCnSwBxJg)) +* [piston3d-gfx_voxel-0.1.1](https://crates.io/crates/piston3d-gfx_voxel) ([before](https://tools.taskcluster.net/task-inspector/#2nZmq4zORIOdJ-ErCOCmww)) ([after](https://tools.taskcluster.net/task-inspector/#epzWs2zuSiWxfoWyMCv0Kw)) +* [dev-0.0.7](https://crates.io/crates/dev) ([before](https://tools.taskcluster.net/task-inspector/#5hSafPV2RlKlubg7WHniPw)) ([after](https://tools.taskcluster.net/task-inspector/#ITQ6zXYpSAC3_AtmMe4xRw)) +* [rustty-0.1.3](https://crates.io/crates/rustty) ([before](https://tools.taskcluster.net/task-inspector/#jlstxp6mSPqzQ1n3FgHSRA)) ([after](https://tools.taskcluster.net/task-inspector/#HgrQz6UVQ5yCkVX25Py-2w)) +* [skeletal_animation-0.1.1](https://crates.io/crates/skeletal_animation) ([before](https://tools.taskcluster.net/task-inspector/#nyMUzqs6RZKIZJ1v1xcglA)) ([after](https://tools.taskcluster.net/task-inspector/#10lM9Vh5SBa7YD3swbm6pw)) +* [slabmalloc-0.0.1](https://crates.io/crates/slabmalloc) ([before](https://tools.taskcluster.net/task-inspector/#li_vsJY8S9-OKEP_KIzEyQ)) ([after](https://tools.taskcluster.net/task-inspector/#1lcKVbKVQNqkKSfwEKIvkg)) +* [spidev-0.1.0](https://crates.io/crates/spidev) ([before](https://tools.taskcluster.net/task-inspector/#5YidcvWyQ0KSmX_9yHjL5A)) ([after](https://tools.taskcluster.net/task-inspector/#mmDafSdlSIS-xfDvyeIckQ)) +* [sysfs_gpio-0.3.2](https://crates.io/crates/sysfs_gpio) ([before](https://tools.taskcluster.net/task-inspector/#KEO87BJHSB-9wNHvTGgEiQ)) ([after](https://tools.taskcluster.net/task-inspector/#44Qnzq6CSBSrMti4utYEZQ)) +* [texture_packer-0.0.1](https://crates.io/crates/texture_packer) ([before](https://tools.taskcluster.net/task-inspector/#-yNhXPaFSBK59eEPRBChVw)) ([after](https://tools.taskcluster.net/task-inspector/#dY5YnW-uTRuCAxxh93_P1w)) +* [falcon-0.0.1](https://crates.io/crates/falcon) ([before](https://tools.taskcluster.net/task-inspector/#hsFGvgrWTL6yY5JVjm20Sw)) ([after](https://tools.taskcluster.net/task-inspector/#YMYfL2KkTH2fct8CD9nqUg)) +* [filetype-0.2.0](https://crates.io/crates/filetype) ([before](https://tools.taskcluster.net/task-inspector/#bCC3ps_gT6m05BNm5lEnFw)) ([after](https://tools.taskcluster.net/task-inspector/#trGw9uPMTgiuxp-w821ZgA))