Skip to content
File

Blob: src/rust/jsg-test/tests/unwrap.rs

rust180 lines
1// Copyright (c) 2026 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4 
5use jsg::Number;
6use jsg::v8::ToLocalValue;
7 
8#[test]
9fn v8_is_string_returns_true_for_strings() {
10 let harness = crate::Harness::new();
11 harness.run_in_context(|lock, _ctx| {
12 let str_val = "hello".to_local(lock);
13 assert!(str_val.is_string());
14 Ok(())
15 });
16}
17 
18#[test]
19fn v8_is_boolean_returns_true_for_booleans() {
20 let harness = crate::Harness::new();
21 harness.run_in_context(|lock, _ctx| {
22 let bool_true = true.to_local(lock);
23 assert!(bool_true.is_boolean());
24 
25 let bool_false = false.to_local(lock);
26 assert!(bool_false.is_boolean());
27 Ok(())
28 });
29}
30 
31#[test]
32fn v8_is_number_returns_true_for_numbers() {
33 let harness = crate::Harness::new();
34 harness.run_in_context(|lock, _ctx| {
35 let float_val = Number::new(2.5).to_local(lock);
36 assert!(float_val.is_number());
37 
38 let int_val = 42u32.to_local(lock);
39 assert!(int_val.is_number());
40 
41 let num_u8 = 255u8.to_local(lock);
42 assert!(num_u8.is_number());
43 Ok(())
44 });
45}
46 
47#[test]
48fn v8_type_checks_are_mutually_exclusive() {
49 let harness = crate::Harness::new();
50 harness.run_in_context(|lock, _ctx| {
51 // String is only string
52 let s = "test".to_local(lock);
53 assert!(s.is_string() && !s.is_boolean() && !s.is_number());
54 
55 // Boolean is only boolean
56 let b = true.to_local(lock);
57 assert!(!b.is_string() && b.is_boolean() && !b.is_number());
58 
59 // Number is only number
60 let n = Number::new(42.0).to_local(lock);
61 assert!(!n.is_string() && !n.is_boolean() && n.is_number());
62 Ok(())
63 });
64}
65 
66#[test]
67fn v8_unwrap_boolean_returns_correct_values() {
68 let harness = crate::Harness::new();
69 harness.run_in_context(|lock, _ctx| {
70 let bool_true = true.to_local(lock);
71 // SAFETY: The isolate is locked and the value is a valid V8 local handle.
72 let unwrapped_true =
73 unsafe { jsg::v8::ffi::unwrap_boolean(lock.isolate().as_ffi(), bool_true.into_ffi()) };
74 assert!(unwrapped_true);
75 
76 let bool_false = false.to_local(lock);
77 // SAFETY: The isolate is locked and the value is a valid V8 local handle.
78 let unwrapped_false =
79 unsafe { jsg::v8::ffi::unwrap_boolean(lock.isolate().as_ffi(), bool_false.into_ffi()) };
80 assert!(!unwrapped_false);
81 Ok(())
82 });
83}
84 
85#[test]
86fn v8_unwrap_number_returns_correct_values() {
87 let harness = crate::Harness::new();
88 harness.run_in_context(|lock, _ctx| {
89 let num = Number::new(2.5).to_local(lock);
90 // SAFETY: The isolate is locked and the value is a valid V8 local handle.
91 let unwrapped =
92 unsafe { jsg::v8::ffi::unwrap_number(lock.isolate().as_ffi(), num.into_ffi()) };
93 assert!((unwrapped - 2.5).abs() < f64::EPSILON);
94 
95 let zero = Number::new(0.0).to_local(lock);
96 // SAFETY: The isolate is locked and the value is a valid V8 local handle.
97 let unwrapped_zero =
98 unsafe { jsg::v8::ffi::unwrap_number(lock.isolate().as_ffi(), zero.into_ffi()) };
99 assert!(unwrapped_zero.abs() < f64::EPSILON);
100 
101 let negative = Number::new(-42.5).to_local(lock);
102 // SAFETY: The isolate is locked and the value is a valid V8 local handle.
103 let unwrapped_neg =
104 unsafe { jsg::v8::ffi::unwrap_number(lock.isolate().as_ffi(), negative.into_ffi()) };
105 assert!((unwrapped_neg - (-42.5)).abs() < f64::EPSILON);
106 Ok(())
107 });
108}
109 
110#[test]
111fn v8_unwrap_string_returns_correct_values() {
112 let harness = crate::Harness::new();
113 harness.run_in_context(|lock, _ctx| {
114 let s = "hello world".to_local(lock);
115 // SAFETY: The isolate is locked and the value is a valid V8 local handle.
116 let unwrapped =
117 unsafe { jsg::v8::ffi::unwrap_string(lock.isolate().as_ffi(), s.into_ffi()) };
118 assert_eq!(unwrapped.as_str(), "hello world");
119 
120 let empty = "".to_local(lock);
121 // SAFETY: The isolate is locked and the value is a valid V8 local handle.
122 let unwrapped_empty =
123 unsafe { jsg::v8::ffi::unwrap_string(lock.isolate().as_ffi(), empty.into_ffi()) };
124 assert_eq!(unwrapped_empty.as_str(), "");
125 
126 let unicode = "こんにちは".to_local(lock);
127 // SAFETY: The isolate is locked and the value is a valid V8 local handle.
128 let unwrapped_unicode =
129 unsafe { jsg::v8::ffi::unwrap_string(lock.isolate().as_ffi(), unicode.into_ffi()) };
130 assert_eq!(unwrapped_unicode.as_str(), "こんにちは");
131 Ok(())
132 });
133}
134 
135/// Tests that `unwrap_resource` rejects a C++ JSG object (tagged with `WORKERD_WRAPPABLE_TAG`).
136///
137/// Rust wrappables use `WORKERD_RUST_WRAPPABLE_TAG` (0xeb05), while C++ JSG objects use
138/// `WORKERD_WRAPPABLE_TAG` (0xeb04). Attempting to unwrap a C++ object through the Rust path
139/// must return nullptr to prevent reading garbage from non-existent `data[2]` fields.
140#[test]
141fn unwrap_resource_rejects_cpp_tagged_object() {
142 let harness = crate::Harness::new();
143 harness.run_in_context(|lock, _ctx| {
144 let cpp_obj = crate::Harness::create_cpp_tagged_object(lock);
145 
146 // unwrap_resource returns nullptr because the object has the C++ tag, not the Rust tag.
147 let result =
148 // SAFETY: isolate is valid and locked, value is a valid Local.
149 unsafe { jsg::v8::ffi::unwrap_resource(lock.isolate().as_ffi(), cpp_obj.into_ffi()) };
150 assert!(
151 result.get().is_null(),
152 "unwrap_resource should return null for a C++ tagged object"
153 );
154 
155 Ok(())
156 });
157}
158 
159/// Tests that `unwrap_resource` rejects a plain JS object with no internal fields.
160///
161/// A plain `{}` object has no wrappable tag at all. This verifies we don't crash on
162/// objects that were never wrapped by either the C++ or Rust JSG layer.
163#[test]
164fn unwrap_resource_rejects_plain_js_object() {
165 let harness = crate::Harness::new();
166 harness.run_in_context(|lock, ctx| {
167 let plain_obj = ctx.eval_raw("({})").unwrap();
168 
169 let result =
170 // SAFETY: isolate is valid and locked, value is a valid Local.
171 unsafe { jsg::v8::ffi::unwrap_resource(lock.isolate().as_ffi(), plain_obj.into_ffi()) };
172 assert!(
173 result.get().is_null(),
174 "unwrap_resource should return null for a plain JS object"
175 );
176 
177 Ok(())
178 });
179}