From 9dc3ae8523364b8df6b8e92346907d1020e80d33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Thu, 18 May 2023 12:49:56 +0200 Subject: [PATCH] fix(napi): BigInt related APIs (#19174) Doesn't make the API bullet-proof and there are some TODOs left, but greatly improves the situation. Tests were ported from Node.js. Closes https://github.com/denoland/deno/issues/18276. --- cli/napi/js_native_api.rs | 48 +++++--- test_napi/bigint_test.js | 63 +++++++++++ test_napi/src/bigint.rs | 205 ++++++++++++++++++++++++++++++++++ test_napi/src/lib.rs | 2 + test_napi/tests/napi_tests.rs | 2 + 5 files changed, 307 insertions(+), 13 deletions(-) create mode 100644 test_napi/bigint_test.js create mode 100644 test_napi/src/bigint.rs diff --git a/cli/napi/js_native_api.rs b/cli/napi/js_native_api.rs index ce598cc98d..b05b15e120 100644 --- a/cli/napi/js_native_api.rs +++ b/cli/napi/js_native_api.rs @@ -200,8 +200,8 @@ fn napi_create_bigint_uint64( fn napi_create_bigint_words( env: *mut Env, sign_bit: bool, - words: *const u64, word_count: usize, + words: *const u64, result: *mut napi_value, ) -> Result { check_env!(env); @@ -950,12 +950,17 @@ fn napi_get_value_bigint_int64( env: *mut Env, value: napi_value, result: *mut i64, + lossless: *mut bool, ) -> Result { check_env!(env); let env = unsafe { &mut *env }; let value = napi_value_unchecked(value); let bigint = value.to_big_int(&mut env.scope()).unwrap(); - *result = bigint.i64_value().0; + let (result_, lossless_) = bigint.i64_value(); + *result = result_; + *lossless = lossless_; + // TODO(bartlomieju): + // napi_clear_last_error() Ok(()) } @@ -964,12 +969,17 @@ fn napi_get_value_bigint_uint64( env: *mut Env, value: napi_value, result: *mut u64, + lossless: *mut bool, ) -> Result { check_env!(env); let env = unsafe { &mut *env }; let value = napi_value_unchecked(value); let bigint = value.to_big_int(&mut env.scope()).unwrap(); - *result = bigint.u64_value().0; + let (result_, lossless_) = bigint.u64_value(); + *result = result_; + *lossless = lossless_; + // TODO(bartlomieju): + // napi_clear_last_error() Ok(()) } @@ -978,24 +988,36 @@ fn napi_get_value_bigint_words( env: *mut Env, value: napi_value, sign_bit: *mut i32, - size: *mut usize, - out_words: *mut u64, + word_count: *mut usize, + words: *mut u64, ) -> Result { check_env!(env); + // TODO(bartlomieju): + // check_arg!(env, value); + check_arg!(env, word_count); let env = unsafe { &mut *env }; let value = napi_value_unchecked(value); - let bigint = value.to_big_int(&mut env.scope()).unwrap(); + let big = match value.to_big_int(&mut env.scope()) { + Some(b) => b, + None => return Err(Error::BigIntExpected), + }; + let word_count_int; - let out_words = std::slice::from_raw_parts_mut(out_words, *size); - let mut words = Vec::with_capacity(bigint.word_count()); - let (sign, _) = bigint.to_words_array(words.as_mut_slice()); - *sign_bit = sign as i32; - - for (i, word) in out_words.iter_mut().enumerate() { - *word = words[i]; + if sign_bit.is_null() && words.is_null() { + word_count_int = big.word_count(); + } else { + check_arg!(env, sign_bit); + check_arg!(env, words); + let out_words = std::slice::from_raw_parts_mut(words, *word_count); + let (sign, slice_) = big.to_words_array(out_words); + word_count_int = slice_.len(); + *sign_bit = sign as i32; } + *word_count = word_count_int; + // TODO(bartlomieju): + // napi_clear_last_error() Ok(()) } diff --git a/test_napi/bigint_test.js b/test_napi/bigint_test.js new file mode 100644 index 0000000000..8d05f957d3 --- /dev/null +++ b/test_napi/bigint_test.js @@ -0,0 +1,63 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +import { assertEquals, assertThrows, loadTestLibrary } from "./common.js"; + +const bi = loadTestLibrary(); + +Deno.test("cases", function () { + const cases = [ + 0n, + -0n, + 1n, + -1n, + 100n, + 2121n, + -1233n, + 986583n, + -976675n, + 98765432213456789876546896323445679887645323232436587988766545658n, + -4350987086545760976737453646576078997096876957864353245245769809n, + ]; + + for (const num of cases) { + if (num > -(2n ** 63n) && num < 2n ** 63n) { + assertEquals(bi.testInt64(num), num); + assertEquals(bi.isLossless(num, true), true); + } else { + assertEquals(bi.isLossless(num, true), false); + } + + if (num >= 0 && num < 2n ** 64n) { + assertEquals(bi.testUint64(num), num); + assertEquals(bi.isLossless(num, false), true); + } else { + assertEquals(bi.isLossless(num, false), false); + } + + assertEquals(bi.testWords(num), num); + } +}); + +Deno.test( + // TODO(bartlomieju): fix this test + { ignore: true }, + function tooBigBigInt() { + assertThrows( + () => bi.createTooBigBigInt(), + Error, + "Invalid argument", + ); + }, +); + +Deno.test( + // TODO(bartlomieju): fix this test + { ignore: true }, + function exceptionForwarding() { + assertThrows( + () => bi.makeBigIntWordsThrow(), + Error, + "Maximum BigInt size exceeded", + ); + }, +); diff --git a/test_napi/src/bigint.rs b/test_napi/src/bigint.rs new file mode 100644 index 0000000000..e901e342ae --- /dev/null +++ b/test_napi/src/bigint.rs @@ -0,0 +1,205 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +use crate::assert_napi_ok; +use crate::cstr; +use crate::napi_get_callback_info; +use crate::napi_new_property; +use napi_sys::Status::napi_pending_exception; +use napi_sys::ValueType::napi_bigint; +use napi_sys::*; +use std::ptr; + +extern "C" fn is_lossless( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, argc, _) = napi_get_callback_info!(env, info, 2); + assert_eq!(argc, 2); + + let mut is_signed = false; + assert_napi_ok!(napi_get_value_bool(env, args[1], &mut is_signed)); + + let mut lossless = false; + + if is_signed { + let mut input: i64 = 0; + assert_napi_ok!(napi_get_value_bigint_int64( + env, + args[0], + &mut input, + &mut lossless + )); + } else { + let mut input: u64 = 0; + assert_napi_ok!(napi_get_value_bigint_uint64( + env, + args[0], + &mut input, + &mut lossless + )); + } + + let mut output: napi_value = ptr::null_mut(); + assert_napi_ok!(napi_get_boolean(env, lossless, &mut output)); + + output +} + +extern "C" fn test_int64( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, _argc, _) = napi_get_callback_info!(env, info, 2); + + let mut ty = -1; + assert_napi_ok!(napi_typeof(env, args[0], &mut ty)); + assert_eq!(ty, napi_bigint); + + let mut input: i64 = 0; + let mut lossless = false; + assert_napi_ok!(napi_get_value_bigint_int64( + env, + args[0], + &mut input, + &mut lossless + )); + + let mut output: napi_value = ptr::null_mut(); + assert_napi_ok!(napi_create_bigint_int64(env, input, &mut output)); + + output +} + +extern "C" fn test_uint64( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, _argc, _) = napi_get_callback_info!(env, info, 2); + + let mut ty = -1; + assert_napi_ok!(napi_typeof(env, args[0], &mut ty)); + assert_eq!(ty, napi_bigint); + + let mut input: u64 = 0; + let mut lossless = false; + assert_napi_ok!(napi_get_value_bigint_uint64( + env, + args[0], + &mut input, + &mut lossless + )); + + let mut output: napi_value = ptr::null_mut(); + assert_napi_ok!(napi_create_bigint_uint64(env, input, &mut output)); + + output +} + +extern "C" fn test_words( + env: napi_env, + info: napi_callback_info, +) -> napi_value { + let (args, _argc, _) = napi_get_callback_info!(env, info, 1); + + let mut ty = -1; + assert_napi_ok!(napi_typeof(env, args[0], &mut ty)); + assert_eq!(ty, napi_bigint); + + let mut expected_work_count = 0; + assert_napi_ok!(napi_get_value_bigint_words( + env, + args[0], + ptr::null_mut(), + &mut expected_work_count, + ptr::null_mut() + )); + + let mut sign_bit = 0; + let mut word_count: usize = 10; + let mut words: Vec = Vec::with_capacity(10); + + assert_napi_ok!(napi_get_value_bigint_words( + env, + args[0], + &mut sign_bit, + &mut word_count, + words.as_mut_ptr(), + )); + + assert_eq!(word_count, expected_work_count); + let mut output: napi_value = ptr::null_mut(); + + assert_napi_ok!(napi_create_bigint_words( + env, + sign_bit, + word_count, + words.as_ptr(), + &mut output, + )); + output +} + +extern "C" fn create_too_big_big_int( + env: napi_env, + _info: napi_callback_info, +) -> napi_value { + let sign_bit = 0; + let word_count = usize::MAX; + let words: Vec = Vec::with_capacity(10); + + let mut output: napi_value = ptr::null_mut(); + let result = unsafe { + napi_create_bigint_words( + env, + sign_bit, + word_count, + words.as_ptr(), + &mut output, + ) + }; + assert_eq!(result, 1); + + output +} + +extern "C" fn make_big_int_words_throw( + env: napi_env, + _info: napi_callback_info, +) -> napi_value { + let words: Vec = Vec::with_capacity(10); + let mut output = ptr::null_mut(); + + let status = unsafe { + napi_create_bigint_words(env, 0, usize::MAX, words.as_ptr(), &mut output) + }; + + if status != napi_pending_exception { + unsafe { + napi_throw_error( + env, + ptr::null_mut(), + cstr!("Expected status 'napi_pending_exception'"), + ) + }; + } + + ptr::null_mut() +} + +pub fn init(env: napi_env, exports: napi_value) { + let properties = &[ + napi_new_property!(env, "isLossless", is_lossless), + napi_new_property!(env, "testInt64", test_int64), + napi_new_property!(env, "testUint64", test_uint64), + napi_new_property!(env, "testWords", test_words), + napi_new_property!(env, "createTooBigBigInt", create_too_big_big_int), + napi_new_property!(env, "makeBigIntWordsThrow", make_big_int_words_throw), + ]; + + assert_napi_ok!(napi_define_properties( + env, + exports, + properties.len(), + properties.as_ptr() + )); +} diff --git a/test_napi/src/lib.rs b/test_napi/src/lib.rs index dba9f65a5c..a5a9866ce1 100644 --- a/test_napi/src/lib.rs +++ b/test_napi/src/lib.rs @@ -9,6 +9,7 @@ use napi_sys::*; pub mod array; pub mod arraybuffer; pub mod r#async; +pub mod bigint; pub mod callback; pub mod coerce; pub mod date; @@ -156,6 +157,7 @@ unsafe extern "C" fn napi_register_module_v1( date::init(env, exports); tsfn::init(env, exports); mem::init(env, exports); + bigint::init(env, exports); init_cleanup_hook(env, exports); diff --git a/test_napi/tests/napi_tests.rs b/test_napi/tests/napi_tests.rs index 747f6aa276..3e39894361 100644 --- a/test_napi/tests/napi_tests.rs +++ b/test_napi/tests/napi_tests.rs @@ -26,6 +26,7 @@ fn napi_tests() { let output = deno_cmd() .current_dir(test_util::napi_tests_path()) + .env("RUST_BACKTRACE", "1") .arg("test") .arg("--allow-read") .arg("--allow-env") @@ -39,6 +40,7 @@ fn napi_tests() { let stderr = std::str::from_utf8(&output.stderr).unwrap(); if !output.status.success() { + eprintln!("exit code {:?}", output.status.code()); println!("stdout {stdout}"); println!("stderr {stderr}"); }