Skip to content

Commit

Permalink
util: Detect whether the string is one byte representation or not.
Browse files Browse the repository at this point in the history
References: #56090
  • Loading branch information
theweipeng committed Dec 21, 2024
1 parent c4aa34a commit 6f30b0e
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 0 deletions.
20 changes: 20 additions & 0 deletions doc/api/v8.md
Original file line number Diff line number Diff line change
Expand Up @@ -1304,6 +1304,26 @@ setTimeout(() => {
}, 1000);
```

## `v8.isOneByteRepresentation(content)`

<!-- YAML
added: REPLACEME
-->

* `content` {string}
* Returns: {boolean}

Returns `true` if `content` uses one byte as the underlying representation Otherwise, returns `false`.

```js
const { isOneByteRepresentation } = require('node:v8');

isOneByteRepresentation('hello world');
// Returns: true
isOneByteRepresentation('你好');
// Returns: false
```

[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
[Hook Callbacks]: #hook-callbacks
[V8]: https://developers.google.com/v8/
Expand Down
13 changes: 13 additions & 0 deletions lib/v8.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ const binding = internalBinding('v8');
const {
cachedDataVersionTag,
setFlagsFromString: _setFlagsFromString,
isStringOneByteRepresentation: _isStringOneByteRepresentation,
updateHeapStatisticsBuffer,
updateHeapSpaceStatisticsBuffer,
updateHeapCodeStatisticsBuffer,
Expand Down Expand Up @@ -155,6 +156,17 @@ function setFlagsFromString(flags) {
_setFlagsFromString(flags);
}

/**
* Return whether this string uses one byte as underlying representation or not.
* @param {string} content
* @returns {boolean}
*/
function isStringOneByteRepresentation(content) {
validateString(content, 'content');
return _isStringOneByteRepresentation(content);
}


/**
* Gets the current V8 heap statistics.
* @returns {{
Expand Down Expand Up @@ -439,4 +451,5 @@ module.exports = {
startupSnapshot,
setHeapSnapshotNearHeapLimit,
GCProfiler,
isStringOneByteRepresentation,
};
4 changes: 4 additions & 0 deletions src/node_external_reference.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ namespace node {

using CFunctionCallbackWithOneByteString =
uint32_t (*)(v8::Local<v8::Value>, const v8::FastOneByteString&);

using CFunctionCallbackReturnBool = bool (*)(v8::Local<v8::Value> unused,
v8::Local<v8::Value> receiver);
using CFunctionCallback = void (*)(v8::Local<v8::Value> unused,
v8::Local<v8::Value> receiver);
using CFunctionCallbackReturnDouble =
Expand Down Expand Up @@ -90,6 +93,7 @@ class ExternalReferenceRegistry {
#define ALLOWED_EXTERNAL_REFERENCE_TYPES(V) \
V(CFunctionCallback) \
V(CFunctionCallbackWithOneByteString) \
V(CFunctionCallbackReturnBool) \
V(CFunctionCallbackReturnDouble) \
V(CFunctionCallbackReturnInt32) \
V(CFunctionCallbackValueReturnDouble) \
Expand Down
30 changes: 30 additions & 0 deletions src/node_v8.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
namespace node {
namespace v8_utils {
using v8::Array;
using v8::CFunction;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
Expand Down Expand Up @@ -238,6 +239,25 @@ void SetFlagsFromString(const FunctionCallbackInfo<Value>& args) {
V8::SetFlagsFromString(*flags, static_cast<size_t>(flags.length()));
}


static void IsStringOneByteRepresentation(
const FunctionCallbackInfo<Value>& args) {
CHECK_EQ(args.Length(), 1);
CHECK(args[0]->IsString());
bool is_one_byte = args[0].As<String>()->IsOneByte();
args.GetReturnValue().Set(is_one_byte);
}

static bool FastIsStringOneByteRepresentation(Local<Value> receiver,
const Local<Value> target) {
CHECK(target->IsString());
return target.As<String>()->IsOneByte();
}

CFunction fast_is_string_one_byte_representation_(
CFunction::Make(FastIsStringOneByteRepresentation));


static const char* GetGCTypeName(v8::GCType gc_type) {
switch (gc_type) {
case v8::GCType::kGCTypeScavenge:
Expand Down Expand Up @@ -479,6 +499,13 @@ void Initialize(Local<Object> target,
// Export symbols used by v8.setFlagsFromString()
SetMethod(context, target, "setFlagsFromString", SetFlagsFromString);

// Export symbols used by v8.isStringOneByteRepresentation()
SetFastMethodNoSideEffect(context,
target,
"isStringOneByteRepresentation",
IsStringOneByteRepresentation,
&fast_is_string_one_byte_representation_);

// GCProfiler
Local<FunctionTemplate> t =
NewFunctionTemplate(env->isolate(), GCProfiler::New);
Expand All @@ -498,6 +525,9 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(GCProfiler::New);
registry->Register(GCProfiler::Start);
registry->Register(GCProfiler::Stop);
registry->Register(IsStringOneByteRepresentation);
registry->Register(FastIsStringOneByteRepresentation);
registry->Register(fast_is_string_one_byte_representation_.GetTypeInfo());
}

} // namespace v8_utils
Expand Down
37 changes: 37 additions & 0 deletions test/parallel/test-v8-string-is-one-byte-representation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Flags: --expose-internals
'use strict';
require('../common');
const assert = require('assert');
const { isStringOneByteRepresentation } = require('v8');

[
undefined,
null,
false,
5n,
5,
Symbol(),
() => {},
{},
].forEach((value) => {
assert.throws(
() => { isStringOneByteRepresentation(value); },
/The "content" argument must be of type string/
);
});

{
const latin1String = 'hello world!';
// Run this inside a for loop to trigger the fast API
for (let i = 0; i < 10_000; i++) {
assert.strictEqual(isStringOneByteRepresentation(latin1String), true);
}
}

{
const utf16String = '你好😀😃';
// Run this inside a for loop to trigger the fast API
for (let i = 0; i < 10_000; i++) {
assert.strictEqual(isStringOneByteRepresentation(utf16String), false);
}
}

0 comments on commit 6f30b0e

Please sign in to comment.