Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions mozjs/benches/latin1_string_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use criterion::{
criterion_group, criterion_main, BenchmarkGroup, BenchmarkId, Criterion, Throughput,
};
use mozjs::context::JSContext;
use mozjs::conversions::jsstr_to_string;
use mozjs::conversions::jsstr_to_string_safe;
use mozjs::glue::{CreateJSExternalStringCallbacks, JSExternalStringCallbacksTraps};
use mozjs::jsapi::OnNewGlobalHookOption;
use mozjs::realm::AutoRealm;
Expand Down Expand Up @@ -52,9 +52,7 @@ fn bench_str_repetition(
&latin1_jsstr,
|b, js_str| {
b.iter(|| {
unsafe {
jsstr_to_string(context.raw_cx(), NonNull::new(js_str.get()).unwrap())
};
jsstr_to_string_safe(context, NonNull::new(js_str.get()).unwrap());
})
},
);
Expand Down
40 changes: 30 additions & 10 deletions mozjs/src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,17 @@

use crate::error::throw_type_error;
use crate::jsapi::AssertSameCompartment;
use crate::jsapi::NewArrayObject1;
use crate::jsapi::JS;
use crate::jsapi::{ForOfIterator, ForOfIterator_NonIterableBehavior};
use crate::jsapi::{Heap, JS_DefineElement, JS_GetLatin1StringCharsAndLength};
use crate::jsapi::{Heap, JS_DefineElement};
use crate::jsapi::{JSContext, JSObject, JSString, RootedObject, RootedValue};
use crate::jsapi::{JS_DeprecatedStringHasLatin1Chars, JS_NewStringCopyUTF8N, JSPROP_ENUMERATE};
use crate::jsapi::{JS_GetTwoByteStringCharsAndLength, NewArrayObject1};
use crate::jsval::{BooleanValue, DoubleValue, Int32Value, NullValue, UInt32Value, UndefinedValue};
use crate::jsval::{JSVal, ObjectOrNullValue, ObjectValue, StringValue, SymbolValue};
use crate::rooted;
use crate::rust::maybe_wrap_value;
use crate::rust::wrappers2::{JS_GetLatin1StringCharsAndLength, JS_GetTwoByteStringCharsAndLength};
use crate::rust::{maybe_wrap_object_or_null_value, maybe_wrap_object_value, ToString};
use crate::rust::{HandleValue, MutableHandleValue};
use crate::rust::{ToBoolean, ToInt32, ToInt64, ToNumber, ToUint16, ToUint32, ToUint64};
Expand Down Expand Up @@ -596,12 +597,12 @@ impl FromJSValConvertible for f64 {

/// Converts a `JSString`, encoded in "Latin1" (i.e. U+0000-U+00FF encoded as 0x00-0xFF) into a
/// `String`.
pub unsafe fn latin1_to_string(cx: *mut JSContext, s: NonNull<JSString>) -> String {
assert!(JS_DeprecatedStringHasLatin1Chars(s.as_ptr()));
pub fn latin1_to_string_safe(cx: &crate::context::JSContext, s: NonNull<JSString>) -> String {
assert!(unsafe { JS_DeprecatedStringHasLatin1Chars(s.as_ptr()) });

let mut length = 0;
let chars = unsafe {
let chars = JS_GetLatin1StringCharsAndLength(cx, ptr::null(), s.as_ptr(), &mut length);
let chars = JS_GetLatin1StringCharsAndLength(cx, s.as_ptr(), &mut length);
assert!(!chars.is_null());

slice::from_raw_parts(chars, length as usize)
Expand All @@ -620,18 +621,37 @@ pub unsafe fn latin1_to_string(cx: *mut JSContext, s: NonNull<JSString>) -> Stri
}

/// Converts a `JSString` into a `String`, regardless of used encoding.
pub unsafe fn jsstr_to_string(cx: *mut JSContext, jsstr: NonNull<JSString>) -> String {
if JS_DeprecatedStringHasLatin1Chars(jsstr.as_ptr()) {
return latin1_to_string(cx, jsstr);
pub fn jsstr_to_string_safe(cx: &crate::context::JSContext, jsstr: NonNull<JSString>) -> String {
if unsafe { JS_DeprecatedStringHasLatin1Chars(jsstr.as_ptr()) } {
return latin1_to_string_safe(cx, jsstr);
}

let mut length = 0;
let chars = JS_GetTwoByteStringCharsAndLength(cx, ptr::null(), jsstr.as_ptr(), &mut length);
let chars = unsafe { JS_GetTwoByteStringCharsAndLength(cx, jsstr.as_ptr(), &mut length) };
assert!(!chars.is_null());
let char_vec = slice::from_raw_parts(chars, length as usize);
let char_vec = unsafe { slice::from_raw_parts(chars, length as usize) };
String::from_utf16_lossy(char_vec)
}

/// Converts a `JSString`, encoded in "Latin1" (i.e. U+0000-U+00FF encoded as 0x00-0xFF) into a
/// `String`.
///
/// Use [`latin1_to_string_safe`] if possible as this function will be eventually removed.
pub unsafe fn latin1_to_string(cx: *mut JSContext, s: NonNull<JSString>) -> String {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we perhaps add a deprecated annotation on it, or call it _legacy to make the comment about "will eventually be removed" more obvious at the usage sites?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way we are currently doing is just adding comments without renames/deprecation notice for less friction in servo. But eventually we should start doing this.

// while this can break direct invariants of JSContext
// it is ok in the current usage of this function and it avoids duplicating the code
let cx = crate::context::JSContext::from_ptr(NonNull::new(cx).unwrap());
latin1_to_string_safe(&cx, s)
}

/// Converts a `JSString` into a `String`, regardless of used encoding.
///
/// Use [`jsstr_to_string_safe`] if possible as this function will be eventually removed.
pub unsafe fn jsstr_to_string(cx: *mut JSContext, jsstr: NonNull<JSString>) -> String {
let cx = crate::context::JSContext::from_ptr(NonNull::new(cx).unwrap());
jsstr_to_string_safe(&cx, jsstr)
}

// https://heycam.github.io/webidl/#es-USVString
impl ToJSValConvertible for str {
#[inline]
Expand Down
8 changes: 4 additions & 4 deletions mozjs/tests/external_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use std::ptr::NonNull;

#[cfg(test)]
use mozjs::context::JSContext;
use mozjs::conversions::jsstr_to_string;
use mozjs::conversions::jsstr_to_string_safe;
use mozjs::glue::{CreateJSExternalStringCallbacks, JSExternalStringCallbacksTraps};
use mozjs::jsapi::OnNewGlobalHookOption;
use mozjs::realm::AutoRealm;
Expand Down Expand Up @@ -100,7 +100,7 @@ fn external_string() {
callbacks
));
assert_eq!(
jsstr_to_string(context.raw_cx(), NonNull::new(utf16_jsstr.get()).unwrap()),
jsstr_to_string_safe(context, NonNull::new(utf16_jsstr.get()).unwrap()),
utf16_base
);
}
Expand All @@ -122,7 +122,7 @@ unsafe fn test_latin1_string(context: &mut JSContext, latin1_base: &str) {
callbacks
));
assert_eq!(
jsstr_to_string(context.raw_cx(), NonNull::new(latin1_jsstr.get()).unwrap()),
jsstr_to_string_safe(context, NonNull::new(latin1_jsstr.get()).unwrap()),
latin1_base
);
}
Expand All @@ -143,7 +143,7 @@ unsafe fn test_latin1_string_bytes(context: &mut JSContext, latin1_base: &[u8])
callbacks
));
assert_eq!(
jsstr_to_string(context.raw_cx(), NonNull::new(latin1_jsstr.get()).unwrap()),
jsstr_to_string_safe(context, NonNull::new(latin1_jsstr.get()).unwrap()),
encoding_rs::mem::decode_latin1(latin1_base)
);
}
Expand Down
13 changes: 8 additions & 5 deletions mozjs/tests/stack_gc_vector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use std::ptr::{self, NonNull};
use std::sync::{LazyLock, Mutex};

use mozjs::conversions::jsstr_to_string;
use mozjs::conversions::jsstr_to_string_safe;
use mozjs::gc::StackGCVector;
use mozjs::jsapi::{
CompilationType, Handle, HandleString, HandleValue, JSContext, JSSecurityCallbacks, JSString,
Expand Down Expand Up @@ -34,29 +34,32 @@ unsafe extern "C" fn content_security_policy_allows(
_body_arg: HandleValue,
can_compile_strings: *mut bool,
) -> bool {
// SAFETY: We are in SM hook
let mut cx = unsafe { mozjs::context::JSContext::from_ptr(NonNull::new(cx).unwrap()) };
let cx = &mut cx;
let parameter_strings = SafeHandle::from_raw(parameter_strings);
assert_eq!(parameter_strings.len(), 2);

let string0 = parameter_strings.at(0).expect("should have a value");
let string0 = NonNull::new(*string0).expect("should be non-null");
assert_eq!(jsstr_to_string(cx, string0), "a".to_string());
assert_eq!(jsstr_to_string_safe(cx, string0), "a".to_string());

let string1 = parameter_strings.at(1).expect("should have a value");
let string1 = NonNull::new(*string1).expect("should be non-null");
assert_eq!(jsstr_to_string(cx, string1), "b".to_string());
assert_eq!(jsstr_to_string_safe(cx, string1), "b".to_string());

let parameter_args = SafeHandle::from_raw(parameter_args);
assert_eq!(parameter_args.len(), 2);

let arg0 = parameter_args.at(0).expect("should have a value");
let string0 = arg0.to_string();
let string0 = NonNull::new(string0).expect("should be non-null");
assert_eq!(jsstr_to_string(cx, string0), "a".to_string());
assert_eq!(jsstr_to_string_safe(cx, string0), "a".to_string());

let arg1 = parameter_args.at(1).expect("should have a value");
let string1 = arg1.to_string();
let string1 = NonNull::new(string1).expect("should be non-null");
assert_eq!(jsstr_to_string(cx, string1), "b".to_string());
assert_eq!(jsstr_to_string_safe(cx, string1), "b".to_string());

*RAN_CSP_CALLBACK.lock().unwrap() = true;
*can_compile_strings = true;
Expand Down
Loading