123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
- From: VerteDinde <[email protected]>
- Date: Mon, 24 Jun 2024 21:48:40 -0700
- Subject: fix: add property query interceptors
- This commit cherry-picks an upstream interceptor API change
- from node-v8/canary to accomodate V8's upstream changes to old
- interceptor APIs.
- Node PR: https://github.com/nodejs/node-v8/commit/d1f18b0bf16efbc1e54ba04a54735ce4683cb936
- CL: https://chromium-review.googlesource.com/c/v8/v8/+/5630388
- This patch can be removed when the node change is incorporated into main.
- diff --git a/src/node_contextify.cc b/src/node_contextify.cc
- index 28ba7dbe66a44a43c39e3d75edf0be9513bcf732..0401b968916e5f45d148281c74b7e465e11439b8 100644
- --- a/src/node_contextify.cc
- +++ b/src/node_contextify.cc
- @@ -49,6 +49,7 @@ using v8::FunctionTemplate;
- using v8::HandleScope;
- using v8::IndexedPropertyHandlerConfiguration;
- using v8::Int32;
- +using v8::Intercepted;
- using v8::Isolate;
- using v8::Just;
- using v8::Local;
- @@ -484,14 +485,15 @@ bool ContextifyContext::IsStillInitializing(const ContextifyContext* ctx) {
- }
-
- // static
- -void ContextifyContext::PropertyGetterCallback(
- - Local<Name> property,
- - const PropertyCallbackInfo<Value>& args) {
- +Intercepted ContextifyContext::PropertyGetterCallback(
- + Local<Name> property, const PropertyCallbackInfo<Value>& args) {
- Environment* env = Environment::GetCurrent(args);
- ContextifyContext* ctx = ContextifyContext::Get(args);
-
- // Still initializing
- - if (IsStillInitializing(ctx)) return;
- + if (IsStillInitializing(ctx)) {
- + return Intercepted::kNo;
- + }
-
- Local<Context> context = ctx->context();
- Local<Object> sandbox = ctx->sandbox();
- @@ -515,18 +517,22 @@ void ContextifyContext::PropertyGetterCallback(
- rv = ctx->global_proxy();
-
- args.GetReturnValue().Set(rv);
- + return Intercepted::kYes;
- }
- + return Intercepted::kNo;
- }
-
- // static
- -void ContextifyContext::PropertySetterCallback(
- +Intercepted ContextifyContext::PropertySetterCallback(
- Local<Name> property,
- Local<Value> value,
- - const PropertyCallbackInfo<Value>& args) {
- + const PropertyCallbackInfo<void>& args) {
- ContextifyContext* ctx = ContextifyContext::Get(args);
-
- // Still initializing
- - if (IsStillInitializing(ctx)) return;
- + if (IsStillInitializing(ctx)) {
- + return Intercepted::kNo;
- + }
-
- Local<Context> context = ctx->context();
- PropertyAttribute attributes = PropertyAttribute::None;
- @@ -544,8 +550,9 @@ void ContextifyContext::PropertySetterCallback(
- (static_cast<int>(attributes) &
- static_cast<int>(PropertyAttribute::ReadOnly));
-
- - if (read_only)
- - return;
- + if (read_only) {
- + return Intercepted::kNo;
- + }
-
- // true for x = 5
- // false for this.x = 5
- @@ -564,11 +571,16 @@ void ContextifyContext::PropertySetterCallback(
-
- bool is_declared = is_declared_on_global_proxy || is_declared_on_sandbox;
- if (!is_declared && args.ShouldThrowOnError() && is_contextual_store &&
- - !is_function)
- - return;
- + !is_function) {
- + return Intercepted::kNo;
- + }
-
- - if (!is_declared && property->IsSymbol()) return;
- - if (ctx->sandbox()->Set(context, property, value).IsNothing()) return;
- + if (!is_declared && property->IsSymbol()) {
- + return Intercepted::kNo;
- + }
- + if (ctx->sandbox()->Set(context, property, value).IsNothing()) {
- + return Intercepted::kNo;
- + }
-
- Local<Value> desc;
- if (is_declared_on_sandbox &&
- @@ -582,19 +594,23 @@ void ContextifyContext::PropertySetterCallback(
- // We have to specify the return value for any contextual or get/set
- // property
- if (desc_obj->HasOwnProperty(context, env->get_string()).FromMaybe(false) ||
- - desc_obj->HasOwnProperty(context, env->set_string()).FromMaybe(false))
- + desc_obj->HasOwnProperty(context, env->set_string()).FromMaybe(false)) {
- args.GetReturnValue().Set(value);
- + return Intercepted::kYes;
- + }
- }
- + return Intercepted::kNo;
- }
-
- // static
- -void ContextifyContext::PropertyDescriptorCallback(
- - Local<Name> property,
- - const PropertyCallbackInfo<Value>& args) {
- +Intercepted ContextifyContext::PropertyDescriptorCallback(
- + Local<Name> property, const PropertyCallbackInfo<Value>& args) {
- ContextifyContext* ctx = ContextifyContext::Get(args);
-
- // Still initializing
- - if (IsStillInitializing(ctx)) return;
- + if (IsStillInitializing(ctx)) {
- + return Intercepted::kNo;
- + }
-
- Local<Context> context = ctx->context();
-
- @@ -604,19 +620,23 @@ void ContextifyContext::PropertyDescriptorCallback(
- Local<Value> desc;
- if (sandbox->GetOwnPropertyDescriptor(context, property).ToLocal(&desc)) {
- args.GetReturnValue().Set(desc);
- + return Intercepted::kYes;
- }
- }
- + return Intercepted::kNo;
- }
-
- // static
- -void ContextifyContext::PropertyDefinerCallback(
- +Intercepted ContextifyContext::PropertyDefinerCallback(
- Local<Name> property,
- const PropertyDescriptor& desc,
- - const PropertyCallbackInfo<Value>& args) {
- + const PropertyCallbackInfo<void>& args) {
- ContextifyContext* ctx = ContextifyContext::Get(args);
-
- // Still initializing
- - if (IsStillInitializing(ctx)) return;
- + if (IsStillInitializing(ctx)) {
- + return Intercepted::kNo;
- + }
-
- Local<Context> context = ctx->context();
- Isolate* isolate = context->GetIsolate();
- @@ -635,7 +655,7 @@ void ContextifyContext::PropertyDefinerCallback(
- // If the property is set on the global as neither writable nor
- // configurable, don't change it on the global or sandbox.
- if (is_declared && read_only && dont_delete) {
- - return;
- + return Intercepted::kNo;
- }
-
- Local<Object> sandbox = ctx->sandbox();
- @@ -658,6 +678,9 @@ void ContextifyContext::PropertyDefinerCallback(
- desc.has_set() ? desc.set() : Undefined(isolate).As<Value>());
-
- define_prop_on_sandbox(&desc_for_sandbox);
- + // TODO(https://github.com/nodejs/node/issues/52634): this should return
- + // kYes to behave according to the expected semantics.
- + return Intercepted::kNo;
- } else {
- Local<Value> value =
- desc.has_value() ? desc.value() : Undefined(isolate).As<Value>();
- @@ -669,26 +692,32 @@ void ContextifyContext::PropertyDefinerCallback(
- PropertyDescriptor desc_for_sandbox(value);
- define_prop_on_sandbox(&desc_for_sandbox);
- }
- + // TODO(https://github.com/nodejs/node/issues/52634): this should return
- + // kYes to behave according to the expected semantics.
- + return Intercepted::kNo;
- }
- }
-
- // static
- -void ContextifyContext::PropertyDeleterCallback(
- - Local<Name> property,
- - const PropertyCallbackInfo<Boolean>& args) {
- +Intercepted ContextifyContext::PropertyDeleterCallback(
- + Local<Name> property, const PropertyCallbackInfo<Boolean>& args) {
- ContextifyContext* ctx = ContextifyContext::Get(args);
-
- // Still initializing
- - if (IsStillInitializing(ctx)) return;
- + if (IsStillInitializing(ctx)) {
- + return Intercepted::kNo;
- + }
-
- Maybe<bool> success = ctx->sandbox()->Delete(ctx->context(), property);
-
- - if (success.FromMaybe(false))
- - return;
- + if (success.FromMaybe(false)) {
- + return Intercepted::kNo;
- + }
-
- // Delete failed on the sandbox, intercept and do not delete on
- // the global object.
- args.GetReturnValue().Set(false);
- + return Intercepted::kYes;
- }
-
- // static
- @@ -708,76 +737,84 @@ void ContextifyContext::PropertyEnumeratorCallback(
- }
-
- // static
- -void ContextifyContext::IndexedPropertyGetterCallback(
- - uint32_t index,
- - const PropertyCallbackInfo<Value>& args) {
- +Intercepted ContextifyContext::IndexedPropertyGetterCallback(
- + uint32_t index, const PropertyCallbackInfo<Value>& args) {
- ContextifyContext* ctx = ContextifyContext::Get(args);
-
- // Still initializing
- - if (IsStillInitializing(ctx)) return;
- + if (IsStillInitializing(ctx)) {
- + return Intercepted::kNo;
- + }
-
- - ContextifyContext::PropertyGetterCallback(
- + return ContextifyContext::PropertyGetterCallback(
- Uint32ToName(ctx->context(), index), args);
- }
-
- -
- -void ContextifyContext::IndexedPropertySetterCallback(
- +Intercepted ContextifyContext::IndexedPropertySetterCallback(
- uint32_t index,
- Local<Value> value,
- - const PropertyCallbackInfo<Value>& args) {
- + const PropertyCallbackInfo<void>& args) {
- ContextifyContext* ctx = ContextifyContext::Get(args);
-
- // Still initializing
- - if (IsStillInitializing(ctx)) return;
- + if (IsStillInitializing(ctx)) {
- + return Intercepted::kNo;
- + }
-
- - ContextifyContext::PropertySetterCallback(
- + return ContextifyContext::PropertySetterCallback(
- Uint32ToName(ctx->context(), index), value, args);
- }
-
- // static
- -void ContextifyContext::IndexedPropertyDescriptorCallback(
- - uint32_t index,
- - const PropertyCallbackInfo<Value>& args) {
- +Intercepted ContextifyContext::IndexedPropertyDescriptorCallback(
- + uint32_t index, const PropertyCallbackInfo<Value>& args) {
- ContextifyContext* ctx = ContextifyContext::Get(args);
-
- // Still initializing
- - if (IsStillInitializing(ctx)) return;
- + if (IsStillInitializing(ctx)) {
- + return Intercepted::kNo;
- + }
-
- - ContextifyContext::PropertyDescriptorCallback(
- + return ContextifyContext::PropertyDescriptorCallback(
- Uint32ToName(ctx->context(), index), args);
- }
-
-
- -void ContextifyContext::IndexedPropertyDefinerCallback(
- +Intercepted ContextifyContext::IndexedPropertyDefinerCallback(
- uint32_t index,
- const PropertyDescriptor& desc,
- - const PropertyCallbackInfo<Value>& args) {
- + const PropertyCallbackInfo<void>& args) {
- ContextifyContext* ctx = ContextifyContext::Get(args);
-
- // Still initializing
- - if (IsStillInitializing(ctx)) return;
- + if (IsStillInitializing(ctx)) {
- + return Intercepted::kNo;
- + }
-
- - ContextifyContext::PropertyDefinerCallback(
- + return ContextifyContext::PropertyDefinerCallback(
- Uint32ToName(ctx->context(), index), desc, args);
- }
-
- // static
- -void ContextifyContext::IndexedPropertyDeleterCallback(
- - uint32_t index,
- - const PropertyCallbackInfo<Boolean>& args) {
- +Intercepted ContextifyContext::IndexedPropertyDeleterCallback(
- + uint32_t index, const PropertyCallbackInfo<Boolean>& args) {
- ContextifyContext* ctx = ContextifyContext::Get(args);
-
- // Still initializing
- - if (IsStillInitializing(ctx)) return;
- + if (IsStillInitializing(ctx)) {
- + return Intercepted::kNo;
- + }
-
- Maybe<bool> success = ctx->sandbox()->Delete(ctx->context(), index);
-
- - if (success.FromMaybe(false))
- - return;
- + if (success.FromMaybe(false)) {
- + return Intercepted::kNo;
- + }
-
- // Delete failed on the sandbox, intercept and do not delete on
- // the global object.
- args.GetReturnValue().Set(false);
- + return Intercepted::kYes;
- }
-
- void ContextifyScript::CreatePerIsolateProperties(
- diff --git a/src/node_contextify.h b/src/node_contextify.h
- index 10715c7eb07715cc11e49734bd54747dad95f6a4..49b9fabb399aed962e0d29e784a25ca4e9780a8f 100644
- --- a/src/node_contextify.h
- +++ b/src/node_contextify.h
- @@ -111,42 +111,39 @@ class ContextifyContext : public BaseObject {
- const v8::FunctionCallbackInfo<v8::Value>& args);
- static void WeakCallback(
- const v8::WeakCallbackInfo<ContextifyContext>& data);
- - static void PropertyGetterCallback(
- + static v8::Intercepted PropertyGetterCallback(
- v8::Local<v8::Name> property,
- const v8::PropertyCallbackInfo<v8::Value>& args);
- - static void PropertySetterCallback(
- + static v8::Intercepted PropertySetterCallback(
- v8::Local<v8::Name> property,
- v8::Local<v8::Value> value,
- - const v8::PropertyCallbackInfo<v8::Value>& args);
- - static void PropertyDescriptorCallback(
- + const v8::PropertyCallbackInfo<void>& args);
- + static v8::Intercepted PropertyDescriptorCallback(
- v8::Local<v8::Name> property,
- const v8::PropertyCallbackInfo<v8::Value>& args);
- - static void PropertyDefinerCallback(
- + static v8::Intercepted PropertyDefinerCallback(
- v8::Local<v8::Name> property,
- const v8::PropertyDescriptor& desc,
- - const v8::PropertyCallbackInfo<v8::Value>& args);
- - static void PropertyDeleterCallback(
- + const v8::PropertyCallbackInfo<void>& args);
- + static v8::Intercepted PropertyDeleterCallback(
- v8::Local<v8::Name> property,
- const v8::PropertyCallbackInfo<v8::Boolean>& args);
- static void PropertyEnumeratorCallback(
- const v8::PropertyCallbackInfo<v8::Array>& args);
- - static void IndexedPropertyGetterCallback(
- - uint32_t index,
- - const v8::PropertyCallbackInfo<v8::Value>& args);
- - static void IndexedPropertySetterCallback(
- + static v8::Intercepted IndexedPropertyGetterCallback(
- + uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& args);
- + static v8::Intercepted IndexedPropertySetterCallback(
- uint32_t index,
- v8::Local<v8::Value> value,
- - const v8::PropertyCallbackInfo<v8::Value>& args);
- - static void IndexedPropertyDescriptorCallback(
- - uint32_t index,
- - const v8::PropertyCallbackInfo<v8::Value>& args);
- - static void IndexedPropertyDefinerCallback(
- + const v8::PropertyCallbackInfo<void>& args);
- + static v8::Intercepted IndexedPropertyDescriptorCallback(
- + uint32_t index, const v8::PropertyCallbackInfo<v8::Value>& args);
- + static v8::Intercepted IndexedPropertyDefinerCallback(
- uint32_t index,
- const v8::PropertyDescriptor& desc,
- - const v8::PropertyCallbackInfo<v8::Value>& args);
- - static void IndexedPropertyDeleterCallback(
- - uint32_t index,
- - const v8::PropertyCallbackInfo<v8::Boolean>& args);
- + const v8::PropertyCallbackInfo<void>& args);
- + static v8::Intercepted IndexedPropertyDeleterCallback(
- + uint32_t index, const v8::PropertyCallbackInfo<v8::Boolean>& args);
-
- v8::Global<v8::Context> context_;
- std::unique_ptr<v8::MicrotaskQueue> microtask_queue_;
- diff --git a/src/node_env_var.cc b/src/node_env_var.cc
- index bce7ae07214ddf970a530db29ed6970e14b7a5ed..85f82180d48d6cfd7738cd7b1e504f23b38153e8 100644
- --- a/src/node_env_var.cc
- +++ b/src/node_env_var.cc
- @@ -16,6 +16,7 @@ using v8::DontEnum;
- using v8::FunctionTemplate;
- using v8::HandleScope;
- using v8::Integer;
- +using v8::Intercepted;
- using v8::Isolate;
- using v8::Just;
- using v8::Local;
- @@ -336,24 +337,27 @@ Maybe<bool> KVStore::AssignToObject(v8::Isolate* isolate,
- return Just(true);
- }
-
- -static void EnvGetter(Local<Name> property,
- - const PropertyCallbackInfo<Value>& info) {
- +static Intercepted EnvGetter(Local<Name> property,
- + const PropertyCallbackInfo<Value>& info) {
- Environment* env = Environment::GetCurrent(info);
- CHECK(env->has_run_bootstrapping_code());
- if (property->IsSymbol()) {
- - return info.GetReturnValue().SetUndefined();
- + info.GetReturnValue().SetUndefined();
- + return Intercepted::kYes;
- }
- CHECK(property->IsString());
- MaybeLocal<String> value_string =
- env->env_vars()->Get(env->isolate(), property.As<String>());
- if (!value_string.IsEmpty()) {
- info.GetReturnValue().Set(value_string.ToLocalChecked());
- + return Intercepted::kYes;
- }
- + return Intercepted::kNo;
- }
-
- -static void EnvSetter(Local<Name> property,
- - Local<Value> value,
- - const PropertyCallbackInfo<Value>& info) {
- +static Intercepted EnvSetter(Local<Name> property,
- + Local<Value> value,
- + const PropertyCallbackInfo<void>& info) {
- Environment* env = Environment::GetCurrent(info);
- CHECK(env->has_run_bootstrapping_code());
- // calling env->EmitProcessEnvWarning() sets a variable indicating that
- @@ -369,35 +373,40 @@ static void EnvSetter(Local<Name> property,
- "the "
- "value to a string before setting process.env with it.",
- "DEP0104")
- - .IsNothing())
- - return;
- + .IsNothing()) {
- + return Intercepted::kNo;
- + }
- }
-
- Local<String> key;
- Local<String> value_string;
- if (!property->ToString(env->context()).ToLocal(&key) ||
- !value->ToString(env->context()).ToLocal(&value_string)) {
- - return;
- + return Intercepted::kNo;
- }
-
- env->env_vars()->Set(env->isolate(), key, value_string);
-
- - // Whether it worked or not, always return value.
- - info.GetReturnValue().Set(value);
- + return Intercepted::kYes;
- }
-
- -static void EnvQuery(Local<Name> property,
- - const PropertyCallbackInfo<Integer>& info) {
- +static Intercepted EnvQuery(Local<Name> property,
- + const PropertyCallbackInfo<Integer>& info) {
- Environment* env = Environment::GetCurrent(info);
- CHECK(env->has_run_bootstrapping_code());
- if (property->IsString()) {
- int32_t rc = env->env_vars()->Query(env->isolate(), property.As<String>());
- - if (rc != -1) info.GetReturnValue().Set(rc);
- + if (rc != -1) {
- + // Return attributes for the property.
- + info.GetReturnValue().Set(v8::None);
- + return Intercepted::kYes;
- + }
- }
- + return Intercepted::kNo;
- }
-
- -static void EnvDeleter(Local<Name> property,
- - const PropertyCallbackInfo<Boolean>& info) {
- +static Intercepted EnvDeleter(Local<Name> property,
- + const PropertyCallbackInfo<Boolean>& info) {
- Environment* env = Environment::GetCurrent(info);
- CHECK(env->has_run_bootstrapping_code());
- if (property->IsString()) {
- @@ -407,6 +416,7 @@ static void EnvDeleter(Local<Name> property,
- // process.env never has non-configurable properties, so always
- // return true like the tc39 delete operator.
- info.GetReturnValue().Set(true);
- + return Intercepted::kYes;
- }
-
- static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) {
- @@ -417,9 +427,9 @@ static void EnvEnumerator(const PropertyCallbackInfo<Array>& info) {
- env->env_vars()->Enumerate(env->isolate()));
- }
-
- -static void EnvDefiner(Local<Name> property,
- - const PropertyDescriptor& desc,
- - const PropertyCallbackInfo<Value>& info) {
- +static Intercepted EnvDefiner(Local<Name> property,
- + const PropertyDescriptor& desc,
- + const PropertyCallbackInfo<void>& info) {
- Environment* env = Environment::GetCurrent(info);
- if (desc.has_value()) {
- if (!desc.has_writable() ||
- @@ -430,6 +440,7 @@ static void EnvDefiner(Local<Name> property,
- "configurable, writable,"
- " and enumerable "
- "data descriptor");
- + return Intercepted::kYes;
- } else if (!desc.configurable() ||
- !desc.enumerable() ||
- !desc.writable()) {
- @@ -438,6 +449,7 @@ static void EnvDefiner(Local<Name> property,
- "configurable, writable,"
- " and enumerable "
- "data descriptor");
- + return Intercepted::kYes;
- } else {
- return EnvSetter(property, desc.value(), info);
- }
- @@ -447,12 +459,14 @@ static void EnvDefiner(Local<Name> property,
- "'process.env' does not accept an"
- " accessor(getter/setter)"
- " descriptor");
- + return Intercepted::kYes;
- } else {
- THROW_ERR_INVALID_OBJECT_DEFINE_PROPERTY(env,
- "'process.env' only accepts a "
- "configurable, writable,"
- " and enumerable "
- "data descriptor");
- + return Intercepted::kYes;
- }
- }
-
- diff --git a/src/node_external_reference.h b/src/node_external_reference.h
- index c4aba23510872d66b58a1adc88cdd1ee85a86cfe..6d9988810b951771064de523bc20aaf389a9c08a 100644
- --- a/src/node_external_reference.h
- +++ b/src/node_external_reference.h
- @@ -66,16 +66,17 @@ class ExternalReferenceRegistry {
- V(v8::FunctionCallback) \
- V(v8::AccessorNameGetterCallback) \
- V(v8::AccessorNameSetterCallback) \
- - V(v8::GenericNamedPropertyDefinerCallback) \
- - V(v8::GenericNamedPropertyDeleterCallback) \
- - V(v8::GenericNamedPropertyEnumeratorCallback) \
- - V(v8::GenericNamedPropertyQueryCallback) \
- - V(v8::GenericNamedPropertySetterCallback) \
- - V(v8::IndexedPropertySetterCallback) \
- - V(v8::IndexedPropertyDefinerCallback) \
- - V(v8::IndexedPropertyDeleterCallback) \
- - V(v8::IndexedPropertyQueryCallback) \
- - V(v8::IndexedPropertyDescriptorCallback) \
- + V(v8::NamedPropertyGetterCallback) \
- + V(v8::NamedPropertyDefinerCallback) \
- + V(v8::NamedPropertyDeleterCallback) \
- + V(v8::NamedPropertyEnumeratorCallback) \
- + V(v8::NamedPropertyQueryCallback) \
- + V(v8::NamedPropertySetterCallback) \
- + V(v8::IndexedPropertyGetterCallbackV2) \
- + V(v8::IndexedPropertySetterCallbackV2) \
- + V(v8::IndexedPropertyDefinerCallbackV2) \
- + V(v8::IndexedPropertyDeleterCallbackV2) \
- + V(v8::IndexedPropertyQueryCallbackV2) \
- V(const v8::String::ExternalStringResourceBase*)
-
- #define V(ExternalReferenceType) \
|