Skip to content

Commit ff6b1cc

Browse files
author
toasteater
committed
Proper fallback in case of TypeId widening
This adds a proper fallback mechanism using `IndexSet` that should cover all cases where the layout of `TypeId` would not be compatible with that of `usize`. A `type_tag_fallback` feature is added so both paths can be tested in CI. Close #608.
1 parent 529a2fc commit ff6b1cc

File tree

5 files changed

+71
-80
lines changed

5 files changed

+71
-80
lines changed

.travis.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,9 @@ script:
154154
cp ../target/debug/libgdnative_test.so ./project/lib/;
155155
"Godot_v${GODOT_VER}-${GODOT_REL}_linux_headless.64" --path ./project/;
156156
"Godot_v${GODOT_VER}-${GODOT_REL}_linux_headless.64" -e --path ./project/ --run-editor-tests;
157+
cargo build --features=type_tag_fallback;
158+
mkdir ./project/lib;
159+
cp ../target/debug/libgdnative_test.so ./project/lib/;
160+
"Godot_v${GODOT_VER}-${GODOT_REL}_linux_headless.64" --path ./project/;
161+
"Godot_v${GODOT_VER}-${GODOT_REL}_linux_headless.64" -e --path ./project/ --run-editor-tests;
157162
fi

gdnative-core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,15 @@ edition = "2018"
1414
default = ["nativescript"]
1515
gd_test = []
1616
nativescript = ["bitflags", "parking_lot"]
17+
type_tag_fallback = []
1718

1819
[dependencies]
1920
gdnative-sys = { path = "../gdnative-sys", version = "0.9.0" }
2021
libc = "0.2"
2122
approx = "0.3.2"
2223
euclid = "0.22.1"
24+
indexmap = "1.6.0"
25+
ahash = "0.4.5"
2326

2427
gdnative-impl-proc-macros = { path = "../impl/proc_macros", version = "=0.9.0" }
2528

Lines changed: 58 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
use crate::nativescript::NativeClass;
21
use std::any::TypeId;
2+
use std::mem::{align_of, size_of};
3+
4+
use indexmap::IndexSet;
5+
6+
use crate::nativescript::NativeClass;
37

48
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
59
struct Tag {
@@ -18,94 +22,68 @@ impl Tag {
1822
}
1923
}
2024

21-
#[cfg(target_pointer_width = "32")]
22-
pub(crate) use self::boxed_type_tag::*;
25+
/// Whether the type tag can be transmuted to `usize`. `true` if the layouts are compatible
26+
/// on the platform, and `type_tag_fallback` is not enabled.
27+
const USE_TRANSMUTE: bool = cfg!(not(feature = "type_tag_fallback"))
28+
&& size_of::<Tag>() == size_of::<usize>()
29+
&& align_of::<Tag>() == align_of::<usize>();
2330

24-
#[cfg(target_pointer_width = "64")]
25-
pub(crate) use self::transmuted_type_tag::*;
31+
/// Keep track of allocated type tags so they can be freed on cleanup. This should only be
32+
/// accessed from one thread at a time.
33+
static mut TAGS: Option<IndexSet<Tag, ahash::RandomState>> = None;
2634

27-
/// Type tags implemented as boxed pointers. This is required for 32-bit targets because `size_t`
28-
/// is only 32-bits wide there, while `TypeId` is always 64-bit.
29-
#[cfg(target_pointer_width = "32")]
30-
mod boxed_type_tag {
31-
use super::Tag;
32-
use crate::nativescript::NativeClass;
33-
use std::boxed::Box;
35+
/// Top bit of `usize`. Used to prevent producing null type tags which might have special
36+
/// meaning assigned.
37+
const MAGIC: usize = 1usize.rotate_right(1);
38+
/// Rest of `usize`.
39+
const MAGIC_MASK: usize = !MAGIC;
3440

35-
/// Keep track of allocated type tags so they can be freed on cleanup
36-
static mut TAGS: Option<Vec<*const Tag>> = None;
41+
/// Create a new type tag for type `T`. This should only be called from `InitHandle`.
42+
#[inline]
43+
pub(crate) unsafe fn create<T>() -> *const libc::c_void
44+
where
45+
T: NativeClass,
46+
{
47+
let tag = Tag::of::<T>();
3748

38-
/// Create a new type tag for type `T`. This should only be called from `InitHandle`.
39-
#[inline]
40-
pub(crate) unsafe fn create<T>() -> *const libc::c_void
41-
where
42-
T: NativeClass,
43-
{
49+
if USE_TRANSMUTE {
50+
// Safety: USE_TRANSMUTE is only true if layouts match
51+
*(&tag as *const Tag as *const *const libc::c_void)
52+
} else {
4453
// Safety: InitHandle is not Send or Sync, so this will only be called from one thread
45-
let tags = TAGS.get_or_insert_with(Vec::new);
46-
let type_tag = Box::into_raw(Box::new(Tag::of::<T>()));
47-
tags.push(type_tag);
48-
type_tag as *const libc::c_void
49-
}
50-
51-
/// Returns `true` if `tag` corresponds to type `T`. `tag` must be one returned by `create`.
52-
#[inline]
53-
pub(crate) unsafe fn check<T>(tag: *const libc::c_void) -> bool
54-
where
55-
T: NativeClass,
56-
{
57-
Tag::of::<T>() == *(tag as *const Tag)
58-
}
59-
60-
/// Perform any cleanup actions if required. Should only be called from
61-
/// `crate::cleanup_internal_state`. `create` and `check` shouldn't be called after this.
62-
#[inline]
63-
pub(crate) unsafe fn cleanup() {
64-
// Safety: By the time cleanup is called, create shouldn't be called again
65-
if let Some(tags) = TAGS.take() {
66-
for ptr in tags.into_iter() {
67-
std::mem::drop(Box::from_raw(ptr as *mut Tag))
68-
}
69-
}
54+
let tags = TAGS.get_or_insert_with(IndexSet::default);
55+
let (idx, _) = tags.insert_full(tag);
56+
// So we don't produce nulls. We're just assuming that 2^31 types will be
57+
// enough for everyone here.
58+
(idx | MAGIC) as *const libc::c_void
7059
}
7160
}
7261

73-
/// Type tags implemented as transmutes. This is faster on 64-bit targets, and require no
74-
/// allocation, as `TypeId` is `Copy`, and fits in a `size_t` there. This may break in the
75-
/// (probably very unlikely) event that:
76-
///
77-
/// - `TypeId`'s size changes (possible in Rust 1.x as `TypeId` is opaque).
78-
/// - `TypeId` loses `Copy` (only possible in Rust 2.0+).
79-
///
80-
/// Both will be compile errors: `transmute` should fail if the sizes mismatch, and the wrapper
81-
/// type `Tag` derives `Copy`.
82-
#[cfg(target_pointer_width = "64")]
83-
mod transmuted_type_tag {
84-
use super::Tag;
85-
use crate::nativescript::NativeClass;
86-
87-
/// Create a new type tag for type `T`. This should only be called from `InitHandle`.
88-
#[inline]
89-
pub(crate) unsafe fn create<T>() -> *const libc::c_void
90-
where
91-
T: NativeClass,
92-
{
93-
std::mem::transmute::<Tag, *const libc::c_void>(Tag::of::<T>())
94-
}
95-
96-
/// Returns `true` if `tag` corresponds to type `T`. `tag` must be one returned by `create`.
97-
#[inline]
98-
pub(crate) unsafe fn check<T>(tag: *const libc::c_void) -> bool
99-
where
100-
T: NativeClass,
101-
{
102-
Tag::of::<T>() == std::mem::transmute::<*const libc::c_void, Tag>(tag)
62+
/// Returns `true` if `tag` corresponds to type `T`. `tag` must be one returned by `create`.
63+
#[inline]
64+
pub(crate) unsafe fn check<T>(tag: *const libc::c_void) -> bool
65+
where
66+
T: NativeClass,
67+
{
68+
if USE_TRANSMUTE {
69+
// Safety: USE_TRANSMUTE is only true if layouts match
70+
Tag::of::<T>() == *(&tag as *const *const libc::c_void as *const Tag)
71+
} else {
72+
let tags = TAGS.as_ref().expect("tag should be created by `create`");
73+
let idx = tag as usize;
74+
let tag = tags
75+
.get_index(idx & MAGIC_MASK)
76+
.expect("tag should be created by `create`");
77+
Tag::of::<T>() == *tag
10378
}
79+
}
10480

105-
/// Perform any cleanup actions if required. Should only be called from
106-
/// `crate::cleanup_internal_state`. `create` and `check` shouldn't be called after this.
107-
#[inline]
108-
pub(crate) unsafe fn cleanup() {
109-
// do nothing
81+
/// Perform any cleanup actions if required. Should only be called from
82+
/// `crate::cleanup_internal_state`. `create` and `check` shouldn't be called after this.
83+
#[inline]
84+
pub(crate) unsafe fn cleanup() {
85+
// Safety: By the time cleanup is called, create shouldn't be called again
86+
if let Some(tags) = TAGS.take() {
87+
drop(tags);
11088
}
11189
}

gdnative/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ default = ["bindings"]
1616
formatted = ["gdnative-bindings/formatted", "gdnative-bindings/one_class_one_file"]
1717

1818
gd_test = ["gdnative-core/gd_test"]
19+
type_tag_fallback = ["gdnative-core/type_tag_fallback"]
1920
bindings = ["gdnative-bindings"]
2021

2122
[dependencies]

test/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ publish = false
88
[lib]
99
crate-type = ["cdylib"]
1010

11+
[features]
12+
default = []
13+
type_tag_fallback = ["gdnative/type_tag_fallback"]
14+
1115
[dependencies]
1216
gdnative = { path = "../gdnative", features = ["gd_test"] }
1317
gdnative-derive = { path = "../gdnative-derive" }

0 commit comments

Comments
 (0)