Commit 5931cc94 authored by Maya Lekova's avatar Maya Lekova Committed by Commit Bot

Reland "[builtins] Port Proxy set trap to CSA"

This is a reland of a9f517e2
Original change's description:
> [builtins] Port Proxy set trap to CSA
> 
> Bug: v8:6560, v8:6557
> Change-Id: I329794607e8de324fc696652555aaaeafcf519ec
> Reviewed-on: https://chromium-review.googlesource.com/625940
> Reviewed-by: Igor Sheludko <ishell@chromium.org>
> Commit-Queue: Maya Lekova <mslekova@google.com>
> Cr-Commit-Position: refs/heads/master@{#47760}

Bug: v8:6560, v8:6557
Change-Id: I1b32992eac6cc5583a44703eed901e4ad15f1947
Reviewed-on: https://chromium-review.googlesource.com/647447
Commit-Queue: Maya Lekova <mslekova@google.com>
Reviewed-by: 's avatarIgor Sheludko <ishell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#47772}
parent d6d72082
......@@ -1003,8 +1003,6 @@ v8_source_set("v8_builtins_generators") {
"src/builtins/builtins-promise-gen.h",
"src/builtins/builtins-proxy-gen.cc",
"src/builtins/builtins-proxy-gen.h",
"src/builtins/builtins-proxy-helpers-gen.cc",
"src/builtins/builtins-proxy-helpers-gen.h",
"src/builtins/builtins-regexp-gen.cc",
"src/builtins/builtins-regexp-gen.h",
"src/builtins/builtins-sharedarraybuffer-gen.cc",
......
......@@ -778,6 +778,7 @@ namespace internal {
SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
TFS(ProxyGetProperty, kProxy, kName, kReceiverValue) \
TFS(ProxyHasProperty, kProxy, kName) \
TFS(ProxySetProperty, kProxy, kName, kValue, kReceiverValue, kLanguageMode) \
\
/* Reflect */ \
ASM(ReflectApply) \
......
......@@ -322,6 +322,257 @@ TF_BUILTIN(ProxyHasProperty, ProxiesCodeStubAssembler) {
StringConstant("has"), proxy);
}
TF_BUILTIN(ProxyGetProperty, ProxiesCodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext);
Node* proxy = Parameter(Descriptor::kProxy);
Node* name = Parameter(Descriptor::kName);
Node* receiver = Parameter(Descriptor::kReceiverValue);
CSA_ASSERT(this, IsJSProxy(proxy));
// 1. Assert: IsPropertyKey(P) is true.
CSA_ASSERT(this, TaggedIsNotSmi(name));
CSA_ASSERT(this, IsName(name));
CSA_ASSERT(this, Word32Equal(IsPrivateSymbol(name), Int32Constant(0)));
Label throw_proxy_handler_revoked(this, Label::kDeferred),
trap_undefined(this);
// 2. Let handler be O.[[ProxyHandler]].
Node* handler = LoadObjectField(proxy, JSProxy::kHandlerOffset);
// 3. If handler is null, throw a TypeError exception.
GotoIf(IsNull(handler), &throw_proxy_handler_revoked);
// 4. Assert: Type(handler) is Object.
CSA_ASSERT(this, IsJSReceiver(handler));
// 5. Let target be O.[[ProxyTarget]].
Node* target = LoadObjectField(proxy, JSProxy::kTargetOffset);
// 6. Let trap be ? GetMethod(handler, "get").
// 7. If trap is undefined, then (see 7.a below).
Handle<Name> trap_name = factory()->get_string();
Node* trap = GetMethod(context, handler, trap_name, &trap_undefined);
// 8. Let trapResult be ? Call(trap, handler, « target, P, Receiver »).
Node* trap_result = CallJS(
CodeFactory::Call(isolate(), ConvertReceiverMode::kNotNullOrUndefined),
context, trap, handler, target, name, receiver);
// 9. Let targetDesc be ? target.[[GetOwnProperty]](P).
Label return_result(this);
CheckGetSetTrapResult(context, target, proxy, name, trap_result,
&return_result, JSProxy::kGet);
BIND(&return_result);
{
// 11. Return trapResult.
Return(trap_result);
}
BIND(&trap_undefined);
{
// 7.a. Return ? target.[[Get]](P, Receiver).
// TODO(mslekova): Introduce GetPropertyWithReceiver stub
Return(CallRuntime(Runtime::kGetPropertyWithReceiver, context, target, name,
receiver));
}
BIND(&throw_proxy_handler_revoked);
ThrowTypeError(context, MessageTemplate::kProxyRevoked, "get");
}
TF_BUILTIN(ProxySetProperty, ProxiesCodeStubAssembler) {
Node* context = Parameter(Descriptor::kContext);
Node* proxy = Parameter(Descriptor::kProxy);
Node* name = Parameter(Descriptor::kName);
Node* value = Parameter(Descriptor::kValue);
Node* receiver = Parameter(Descriptor::kReceiverValue);
Node* language_mode = Parameter(Descriptor::kLanguageMode);
CSA_ASSERT(this, IsJSProxy(proxy));
// 1. Assert: IsPropertyKey(P) is true.
CSA_ASSERT(this, TaggedIsNotSmi(name));
CSA_ASSERT(this, IsName(name));
Label throw_proxy_handler_revoked(this, Label::kDeferred),
trap_undefined(this), failure(this, Label::kDeferred),
continue_checks(this), success(this),
private_symbol(this, Label::kDeferred);
GotoIf(IsPrivateSymbol(name), &private_symbol);
// 2. Let handler be O.[[ProxyHandler]].
Node* handler = LoadObjectField(proxy, JSProxy::kHandlerOffset);
// 3. If handler is null, throw a TypeError exception.
GotoIfNot(IsJSReceiver(handler), &throw_proxy_handler_revoked);
// 4. Assert: Type(handler) is Object.
CSA_ASSERT(this, IsJSReceiver(handler));
// 5. Let target be O.[[ProxyTarget]].
Node* target = LoadObjectField(proxy, JSProxy::kTargetOffset);
// 6. Let trap be ? GetMethod(handler, "set").
// 7. If trap is undefined, then (see 7.a below).
Handle<Name> set_string = factory()->set_string();
Node* trap = GetMethod(context, handler, set_string, &trap_undefined);
// 8. Let booleanTrapResult be ToBoolean(? Call(trap, handler,
// « target, P, V, Receiver »)).
// 9. If booleanTrapResult is false, return false.
BranchIfToBooleanIsTrue(
CallJS(CodeFactory::Call(isolate(),
ConvertReceiverMode::kNotNullOrUndefined),
context, trap, handler, target, name, value, receiver),
&continue_checks, &failure);
BIND(&continue_checks);
{
// 9. Let targetDesc be ? target.[[GetOwnProperty]](P).
Label return_result(this);
CheckGetSetTrapResult(context, target, proxy, name, value, &success,
JSProxy::kSet);
}
BIND(&failure);
{
Label if_throw(this, Label::kDeferred);
Branch(SmiEqual(language_mode, SmiConstant(STRICT)), &if_throw, &success);
BIND(&if_throw);
ThrowTypeError(context, MessageTemplate::kProxyTrapReturnedFalsishFor,
HeapConstant(set_string), name);
}
// 12. Return true.
BIND(&success);
Return(value);
BIND(&private_symbol);
{
Label failure(this), throw_error(this, Label::kDeferred);
Branch(SmiEqual(language_mode, SmiConstant(STRICT)), &throw_error,
&failure);
BIND(&failure);
Return(UndefinedConstant());
BIND(&throw_error);
ThrowTypeError(context, MessageTemplate::kProxyPrivate);
}
BIND(&trap_undefined);
{
// 7.a. Return ? target.[[Set]](P, V, Receiver).
CallRuntime(Runtime::kSetPropertyWithReceiver, context, target, name, value,
receiver, language_mode);
Return(value);
}
BIND(&throw_proxy_handler_revoked);
ThrowTypeError(context, MessageTemplate::kProxyRevoked, "set");
}
void ProxiesCodeStubAssembler::CheckGetSetTrapResult(
Node* context, Node* target, Node* proxy, Node* name, Node* trap_result,
Label* check_passed, JSProxy::AccessKind access_kind) {
Node* map = LoadMap(target);
VARIABLE(var_value, MachineRepresentation::kTagged);
VARIABLE(var_details, MachineRepresentation::kWord32);
VARIABLE(var_raw_value, MachineRepresentation::kTagged);
Label if_found_value(this), check_in_runtime(this, Label::kDeferred);
Node* instance_type = LoadInstanceType(target);
TryGetOwnProperty(context, target, target, map, instance_type, name,
&if_found_value, &var_value, &var_details, &var_raw_value,
check_passed, &check_in_runtime);
BIND(&if_found_value);
{
Label throw_non_configurable_data(this, Label::kDeferred),
throw_non_configurable_accessor(this, Label::kDeferred),
check_accessor(this), check_data(this);
// If targetDesc is not undefined and targetDesc.[[Configurable]] is
// false, then:
GotoIfNot(IsSetWord32(var_details.value(),
PropertyDetails::kAttributesDontDeleteMask),
check_passed);
// If IsDataDescriptor(targetDesc) is true and
// targetDesc.[[Writable]] is false, then:
BranchIfAccessorPair(var_raw_value.value(), &check_accessor, &check_data);
BIND(&check_data);
{
Node* read_only = IsSetWord32(var_details.value(),
PropertyDetails::kAttributesReadOnlyMask);
GotoIfNot(read_only, check_passed);
// If SameValue(trapResult, targetDesc.[[Value]]) is false,
// throw a TypeError exception.
GotoIfNot(SameValue(trap_result, var_value.value()),
&throw_non_configurable_data);
Goto(check_passed);
}
BIND(&check_accessor);
{
Node* accessor_pair = var_raw_value.value();
if (access_kind == JSProxy::kGet) {
Label continue_check(this, Label::kDeferred);
// 10.b. If IsAccessorDescriptor(targetDesc) is true and
// targetDesc.[[Get]] is undefined, then:
Node* getter =
LoadObjectField(accessor_pair, AccessorPair::kGetterOffset);
// Here we check for null as well because if the getter was never
// defined it's set as null.
GotoIf(IsUndefined(getter), &continue_check);
GotoIf(IsNull(getter), &continue_check);
Goto(check_passed);
// 10.b.i. If trapResult is not undefined, throw a TypeError exception.
BIND(&continue_check);
GotoIfNot(IsUndefined(trap_result), &throw_non_configurable_accessor);
} else {
// 11.b.i. If targetDesc.[[Set]] is undefined, throw a TypeError
// exception.
Node* setter =
LoadObjectField(accessor_pair, AccessorPair::kSetterOffset);
GotoIf(IsUndefined(setter), &throw_non_configurable_accessor);
GotoIf(IsNull(setter), &throw_non_configurable_accessor);
}
Goto(check_passed);
}
BIND(&check_in_runtime);
{
CallRuntime(Runtime::kCheckProxyGetSetTrapResult, context, name, target,
trap_result, SmiConstant(access_kind));
Return(trap_result);
}
BIND(&throw_non_configurable_data);
{
ThrowTypeError(context, MessageTemplate::kProxyGetNonConfigurableData,
name, var_value.value(), trap_result);
}
BIND(&throw_non_configurable_accessor);
{
ThrowTypeError(context, MessageTemplate::kProxyGetNonConfigurableAccessor,
name, trap_result);
}
}
}
void ProxiesCodeStubAssembler::CheckHasTrapResult(Node* context, Node* target,
Node* proxy, Node* name,
Label* check_passed,
......
......@@ -16,6 +16,22 @@ class ProxiesCodeStubAssembler : public CodeStubAssembler {
explicit ProxiesCodeStubAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}
void BranchIfAccessorPair(Node* value, Label* if_accessor_pair,
Label* if_not_accessor_pair) {
GotoIf(TaggedIsSmi(value), if_not_accessor_pair);
Branch(IsAccessorPair(value), if_accessor_pair, if_not_accessor_pair);
}
// ES6 section 9.5.8 [[Get]] ( P, Receiver )
// name should not be an index.
Node* ProxyGetProperty(Node* context, Node* proxy, Node* name,
Node* receiver);
// ES6 section 9.5.9 [[Set]] ( P, V, Receiver )
// name should not be an index.
Node* ProxySetProperty(Node* context, Node* proxy, Node* name, Node* value,
Node* receiver);
protected:
void GotoIfRevokedProxy(Node* object, Label* if_proxy_revoked);
Node* AllocateProxy(Node* target, Node* handler, Node* context);
......@@ -24,6 +40,10 @@ class ProxiesCodeStubAssembler : public CodeStubAssembler {
ParameterMode mode);
void CheckHasTrapResult(Node* context, Node* target, Node* proxy, Node* name,
Label* check_passed, Label* if_bailout);
void CheckGetSetTrapResult(Node* context, Node* target, Node* proxy,
Node* name, Node* trap_result, Label* if_not_found,
JSProxy::AccessKind access_kind);
};
} // namespace internal
......
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/builtins/builtins-proxy-helpers-gen.h"
#include "src/builtins/builtins-utils-gen.h"
namespace v8 {
namespace internal {
TF_BUILTIN(ProxyGetProperty, ProxyAssembler) {
Node* context = Parameter(Descriptor::kContext);
Node* proxy = Parameter(Descriptor::kProxy);
Node* name = Parameter(Descriptor::kName);
Node* receiver = Parameter(Descriptor::kReceiverValue);
CSA_ASSERT(this, IsJSProxy(proxy));
// 1. Assert: IsPropertyKey(P) is true.
CSA_ASSERT(this, IsName(name));
Label throw_proxy_handler_revoked(this, Label::kDeferred),
trap_undefined(this), no_target_desc(this, Label::kDeferred),
trap_not_callable(this, Label::kDeferred);
// 2. Let handler be O.[[ProxyHandler]].
Node* handler = LoadObjectField(proxy, JSProxy::kHandlerOffset);
// 3. If handler is null, throw a TypeError exception.
GotoIf(IsNull(handler), &throw_proxy_handler_revoked);
// 4. Assert: Type(handler) is Object.
CSA_ASSERT(this, IsJSReceiver(handler));
// 5. Let target be O.[[ProxyTarget]].
Node* target = LoadObjectField(proxy, JSProxy::kTargetOffset);
// 6. Let trap be ? GetMethod(handler, "get").
// 7. If trap is undefined, then (see 7.a below).
Handle<Name> trap_name = factory()->get_string();
Node* trap = GetMethod(context, handler, trap_name, &trap_undefined);
GotoIf(TaggedIsSmi(trap), &trap_not_callable);
GotoIfNot(IsCallable(trap), &trap_not_callable);
// 8. Let trapResult be ? Call(trap, handler, « target, P, Receiver »).
Node* trap_result = CallJS(CodeFactory::Call(isolate()), context, trap,
handler, target, name, receiver);
// 9. Let targetDesc be ? target.[[GetOwnProperty]](P).
Label return_result(this);
CheckGetTrapResult(context, target, proxy, name, trap_result, &return_result,
&no_target_desc);
BIND(&return_result);
{
// 11. Return trapResult.
Return(trap_result);
}
BIND(&no_target_desc);
{
CSA_ASSERT(this, IsJSReceiver(target));
CallRuntime(Runtime::kCheckProxyGetTrapResult, context, name, target,
trap_result);
Return(trap_result);
}
BIND(&trap_undefined);
{
// 7.a. Return ? target.[[Get]](P, Receiver).
Return(CallRuntime(Runtime::kGetPropertyWithReceiver, context, target, name,
receiver));
}
BIND(&throw_proxy_handler_revoked);
{ ThrowTypeError(context, MessageTemplate::kProxyRevoked, "get"); }
BIND(&trap_not_callable);
{
ThrowTypeError(context, MessageTemplate::kPropertyNotFunction, trap,
StringConstant("get"), receiver);
}
}
void ProxyAssembler::CheckGetTrapResult(Node* context, Node* target,
Node* proxy, Node* name,
Node* trap_result, Label* check_passed,
Label* if_bailout) {
Node* map = LoadMap(target);
VARIABLE(var_value, MachineRepresentation::kTagged);
VARIABLE(var_details, MachineRepresentation::kWord32);
VARIABLE(var_raw_value, MachineRepresentation::kTagged);
Label if_found_value(this, Label::kDeferred);
Node* instance_type = LoadInstanceType(target);
TryGetOwnProperty(context, proxy, target, map, instance_type, name,
&if_found_value, &var_value, &var_details, &var_raw_value,
check_passed, if_bailout);
BIND(&if_found_value);
{
Label throw_non_configurable_data(this, Label::kDeferred),
throw_non_configurable_accessor(this, Label::kDeferred),
check_accessor(this), check_data(this);
// 10. If targetDesc is not undefined and targetDesc.[[Configurable]] is
// false, then:
GotoIfNot(IsSetWord32(var_details.value(),
PropertyDetails::kAttributesDontDeleteMask),
check_passed);
// 10.a. If IsDataDescriptor(targetDesc) is true and
// targetDesc.[[Writable]] is false, then:
BranchIfAccessorPair(var_raw_value.value(), &check_accessor, &check_data);
BIND(&check_data);
{
Node* read_only = IsSetWord32(var_details.value(),
PropertyDetails::kAttributesReadOnlyMask);
GotoIfNot(read_only, check_passed);
// 10.a.i. If SameValue(trapResult, targetDesc.[[Value]]) is false,
// throw a TypeError exception.
GotoIfNot(SameValue(trap_result, var_value.value()),
&throw_non_configurable_data);
Goto(check_passed);
}
BIND(&check_accessor);
{
// 10.b. If IsAccessorDescriptor(targetDesc) is true and
// targetDesc.[[Get]] is undefined, then:
Node* accessor_pair = var_raw_value.value();
Node* getter =
LoadObjectField(accessor_pair, AccessorPair::kGetterOffset);
// Here we check for null as well because if the getter was never
// defined it's set as null.
GotoIfNot(Word32Or(IsUndefined(getter), IsNull(getter)), check_passed);
// 10.b.i. If trapResult is not undefined, throw a TypeError exception.
GotoIfNot(IsUndefined(trap_result), &throw_non_configurable_accessor);
Goto(check_passed);
}
BIND(&throw_non_configurable_data);
{
ThrowTypeError(context, MessageTemplate::kProxyGetNonConfigurableData,
name, var_value.value(), trap_result);
}
BIND(&throw_non_configurable_accessor);
{
ThrowTypeError(context, MessageTemplate::kProxyGetNonConfigurableAccessor,
name, trap_result);
}
}
}
} // namespace internal
} // namespace v8
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_BUILTINS_BUILTINS_PROXY_HELPERS_GEN_H_
#define V8_BUILTINS_BUILTINS_PROXY_HELPERS_GEN_H_
#include "src/code-stub-assembler.h"
namespace v8 {
namespace internal {
using compiler::Node;
class ProxyAssembler : public CodeStubAssembler {
public:
explicit ProxyAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}
void BranchIfAccessorPair(Node* value, Label* if_accessor_pair,
Label* if_not_accessor_pair) {
GotoIf(TaggedIsSmi(value), if_not_accessor_pair);
Branch(IsAccessorPair(value), if_accessor_pair, if_not_accessor_pair);
}
// ES6 section 9.5.8 [[Get]] ( P, Receiver )
Node* ProxyGetProperty(Node* context, Node* proxy, Node* name,
Node* receiver);
protected:
void CheckGetTrapResult(Node* context, Node* target, Node* proxy, Node* name,
Node* trap_result, Label* if_not_found,
Label* if_bailout);
};
} // namespace internal
} // namespace v8
#endif // V8_BUILTINS_BUILTINS_PROXY_HELPERS_GEN_H_
......@@ -6922,6 +6922,7 @@ void CodeStubAssembler::EmitElementStore(Node* object, Node* key, Node* value,
ElementsKind elements_kind,
KeyedAccessStoreMode store_mode,
Label* bailout) {
CSA_ASSERT(this, Word32BinaryNot(IsJSProxy(object)));
Node* elements = LoadElements(object);
if (IsSmiOrObjectElementsKind(elements_kind) &&
store_mode != STORE_NO_TRANSITION_HANDLE_COW) {
......
......@@ -839,6 +839,10 @@ KeyedAccessStoreMode KeyedStoreICNexus::GetKeyedAccessStoreMode() const {
// Element store with prototype chain check.
Handle<Tuple2> data_handler = Handle<Tuple2>::cast(maybe_code_handler);
handler = handle(Code::cast(data_handler->value2()));
} else if (maybe_code_handler->IsSmi()) {
// Skip proxy handlers.
DCHECK_EQ(*maybe_code_handler, *StoreHandler::StoreProxy(GetIsolate()));
continue;
} else {
// Element store without prototype chain check.
handler = Handle<Code>::cast(maybe_code_handler);
......
......@@ -261,8 +261,8 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase(
Label constant(this), field(this), normal(this, Label::kDeferred),
interceptor(this, Label::kDeferred), nonexistent(this),
accessor(this, Label::kDeferred), proxy(this, Label::kDeferred),
global(this, Label::kDeferred), module_export(this, Label::kDeferred);
accessor(this, Label::kDeferred), global(this, Label::kDeferred),
module_export(this, Label::kDeferred), proxy(this, Label::kDeferred);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kField)), &field);
GotoIf(WordEqual(handler_kind, IntPtrConstant(LoadHandler::kConstant)),
......@@ -370,9 +370,35 @@ void AccessorAssembler::HandleLoadICSmiHandlerCase(
BIND(&proxy);
{
exit_point->ReturnCallStub(
Builtins::CallableFor(isolate(), Builtins::kProxyGetProperty),
p->context, holder, p->name, p->receiver);
VARIABLE(var_index, MachineType::PointerRepresentation());
VARIABLE(var_unique, MachineRepresentation::kTagged);
Label if_index(this), if_unique_name(this),
to_name_failed(this, Label::kDeferred);
if (support_elements == kSupportElements) {
TryToName(p->name, &if_index, &var_index, &if_unique_name, &var_unique,
&to_name_failed);
BIND(&if_unique_name);
exit_point->ReturnCallStub(
Builtins::CallableFor(isolate(), Builtins::kProxyGetProperty),
p->context, holder, var_unique.value(), p->receiver);
BIND(&if_index);
// TODO(mslekova): introduce TryToName that doesn't try to compute
// the intptr index value
Goto(&to_name_failed);
BIND(&to_name_failed);
exit_point->ReturnCallRuntime(Runtime::kGetPropertyWithReceiver,
p->context, holder, p->name, p->receiver);
} else {
exit_point->ReturnCallStub(
Builtins::CallableFor(isolate(), Builtins::kProxyGetProperty),
p->context, holder, p->name, p->receiver);
}
}
BIND(&global);
......@@ -612,7 +638,7 @@ void AccessorAssembler::JumpIfDataProperty(Node* details, Label* writable,
void AccessorAssembler::HandleStoreICHandlerCase(
const StoreICParameters* p, Node* handler, Label* miss,
ElementSupport support_elements) {
LanguageMode language_mode, ElementSupport support_elements) {
Label if_smi_handler(this), if_nonsmi_handler(this);
Label if_proto_handler(this), if_element_handler(this), call_handler(this),
store_global(this);
......@@ -626,10 +652,17 @@ void AccessorAssembler::HandleStoreICHandlerCase(
Node* holder = p->receiver;
Node* handler_word = SmiUntag(handler);
Label if_fast_smi(this), slow(this);
GotoIfNot(
WordEqual(handler_word, IntPtrConstant(StoreHandler::kStoreNormal)),
&if_fast_smi);
Label if_fast_smi(this), if_proxy(this);
STATIC_ASSERT(StoreHandler::kStoreNormal + 1 == StoreHandler::kProxy);
STATIC_ASSERT(StoreHandler::kProxy + 1 == StoreHandler::kKindsNumber);
Node* handler_kind = DecodeWord<StoreHandler::KindBits>(handler_word);
GotoIf(IntPtrLessThan(handler_kind,
IntPtrConstant(StoreHandler::kStoreNormal)),
&if_fast_smi);
GotoIf(WordEqual(handler_kind, IntPtrConstant(StoreHandler::kProxy)),
&if_proxy);
Node* properties = LoadSlowProperties(holder);
......@@ -655,6 +688,9 @@ void AccessorAssembler::HandleStoreICHandlerCase(
BIND(&if_fast_smi);
// Handle non-transitioning field stores.
HandleStoreICSmiHandlerCase(handler_word, holder, p->value, nullptr, miss);
BIND(&if_proxy);
HandleStoreToProxy(p, holder, miss, support_elements, language_mode);
}
BIND(&if_nonsmi_handler);
......@@ -673,7 +709,10 @@ void AccessorAssembler::HandleStoreICHandlerCase(
}
BIND(&if_proto_handler);
{ HandleStoreICProtoHandler(p, handler, miss, support_elements); }
{
HandleStoreICProtoHandler(p, handler, miss, support_elements,
language_mode);
}
// |handler| is a heap object. Must be code, call it.
BIND(&call_handler);
......@@ -762,7 +801,7 @@ void AccessorAssembler::HandleStoreICElementHandlerCase(
void AccessorAssembler::HandleStoreICProtoHandler(
const StoreICParameters* p, Node* handler, Label* miss,
ElementSupport support_elements) {
ElementSupport support_elements, LanguageMode language_mode) {
// IC dispatchers rely on these assumptions to be held.
STATIC_ASSERT(FixedArray::kLengthOffset ==
StoreHandler::kTransitionCellOffset);
......@@ -792,12 +831,12 @@ void AccessorAssembler::HandleStoreICProtoHandler(
VARIABLE(var_transition, MachineRepresentation::kTagged);
Label if_transition(this), if_transition_to_constant(this),
if_store_normal(this);
if_store_normal(this), if_proxy(this), do_store(this);
BIND(&tuple_handler);
{
Node* transition = LoadWeakCellValue(maybe_transition_cell, miss);
var_transition.Bind(transition);
Goto(&if_transition);
Goto(&do_store);
}
BIND(&array_handler);
......@@ -815,7 +854,19 @@ void AccessorAssembler::HandleStoreICProtoHandler(
LoadFixedArrayElement(handler, StoreHandler::kTransitionCellIndex);
Node* transition = LoadWeakCellValue(maybe_transition_cell, miss);
var_transition.Bind(transition);
Goto(&if_transition);
Goto(&do_store);
}
BIND(&do_store);
{
Branch(SmiEqual(smi_or_code, SmiConstant(StoreHandler::kProxy)), &if_proxy,
&if_transition);
}
BIND(&if_proxy);
{
Node* proxy = var_transition.value();
HandleStoreToProxy(p, proxy, miss, support_elements, language_mode);
}
BIND(&if_transition);
......@@ -866,9 +917,8 @@ void AccessorAssembler::HandleStoreICProtoHandler(
DescriptorArray::kEntryValueIndex));
Node* descriptors = LoadMapDescriptors(transition);
CSA_ASSERT(
this,
UintPtrLessThan(descriptor,
LoadAndUntagFixedArrayBaseLength(descriptors)));
this, UintPtrLessThan(descriptor,
LoadAndUntagFixedArrayBaseLength(descriptors)));
Node* constant = LoadFixedArrayElement(descriptors, value_index);
GotoIf(WordNotEqual(p->value, constant), miss);
......@@ -915,6 +965,42 @@ void AccessorAssembler::HandleStoreICProtoHandler(
}
}
void AccessorAssembler::HandleStoreToProxy(const StoreICParameters* p,
Node* proxy, Label* miss,
ElementSupport support_elements,
LanguageMode language_mode) {
VARIABLE(var_index, MachineType::PointerRepresentation());
VARIABLE(var_unique, MachineRepresentation::kTagged);
Label if_index(this), if_unique_name(this),
to_name_failed(this, Label::kDeferred);
if (support_elements == kSupportElements) {
TryToName(p->name, &if_index, &var_index, &if_unique_name, &var_unique,
&to_name_failed);
BIND(&if_unique_name);
CallBuiltin(Builtins::kProxySetProperty, p->context, proxy,
var_unique.value(), p->value, p->receiver,
SmiConstant(language_mode));
Return(p->value);
// The index case is handled earlier by the runtime.
BIND(&if_index);
// TODO(mslekova): introduce TryToName that doesn't try to compute
// the intptr index value
Goto(&to_name_failed);
BIND(&to_name_failed);
TailCallRuntime(Runtime::kSetPropertyWithReceiver, p->context, proxy,
p->name, p->value, p->receiver, SmiConstant(language_mode));
} else {
Node* name = ToName(p->context, p->name);
TailCallBuiltin(Builtins::kProxySetProperty, p->context, proxy, name,
p->value, p->receiver, SmiConstant(language_mode));
}
}
void AccessorAssembler::HandleStoreICSmiHandlerCase(Node* handler_word,
Node* holder, Node* value,
Node* transition,
......@@ -2237,7 +2323,7 @@ void AccessorAssembler::StoreIC(const StoreICParameters* p,
BIND(&if_handler);
{
Comment("StoreIC_if_handler");
HandleStoreICHandlerCase(p, var_handler.value(), &miss);
HandleStoreICHandlerCase(p, var_handler.value(), &miss, language_mode);
}
BIND(&try_polymorphic);
......@@ -2298,7 +2384,8 @@ void AccessorAssembler::KeyedStoreIC(const StoreICParameters* p,
BIND(&if_handler);
{
Comment("KeyedStoreIC_if_handler");
HandleStoreICHandlerCase(p, var_handler.value(), &miss, kSupportElements);
HandleStoreICHandlerCase(p, var_handler.value(), &miss, language_mode,
kSupportElements);
}
BIND(&try_polymorphic);
......
......@@ -93,6 +93,7 @@ class AccessorAssembler : public CodeStubAssembler {
enum ElementSupport { kOnlyProperties, kSupportElements };
void HandleStoreICHandlerCase(
const StoreICParameters* p, Node* handler, Label* miss,
LanguageMode language_mode,
ElementSupport support_elements = kOnlyProperties);
void JumpIfDataProperty(Node* details, Label* writable, Label* readonly);
......@@ -164,7 +165,8 @@ class AccessorAssembler : public CodeStubAssembler {
Node* handler, Label* miss);
void HandleStoreICProtoHandler(const StoreICParameters* p, Node* handler,
Label* miss, ElementSupport support_elements);
Label* miss, ElementSupport support_elements,
LanguageMode language_mode);
// If |transition| is nullptr then the normal field store is generated or
// transitioning store otherwise.
void HandleStoreICSmiHandlerCase(Node* handler_word, Node* holder,
......@@ -175,6 +177,10 @@ class AccessorAssembler : public CodeStubAssembler {
Representation representation, Node* value,
Node* transition, Label* miss);
void HandleStoreToProxy(const StoreICParameters* p, Node* proxy, Label* miss,
ElementSupport support_elements,
LanguageMode language_mode);
// KeyedLoadIC_Generic implementation.
void GenericElementLoad(Node* receiver, Node* receiver_map,
......
......@@ -113,6 +113,11 @@ Handle<Smi> StoreHandler::StoreNormal(Isolate* isolate) {
return handle(Smi::FromInt(config), isolate);
}
Handle<Smi> StoreHandler::StoreProxy(Isolate* isolate) {
int config = KindBits::encode(kProxy);
return handle(Smi::FromInt(config), isolate);
}
Handle<Smi> StoreHandler::StoreField(Isolate* isolate, Kind kind,
int descriptor, FieldIndex field_index,
Representation representation,
......
......@@ -160,8 +160,10 @@ class StoreHandler {
kStoreElement,
kStoreField,
kStoreConstField,
kStoreNormal,
kTransitionToField,
kStoreNormal,
kProxy,
kKindsNumber, // Keep last
// TODO(ishell): remove once constant field tracking is done.
kTransitionToConstant = kStoreConstField
};
......@@ -227,6 +229,9 @@ class StoreHandler {
// Creates a Smi-handler for storing a property to a slow object.
static inline Handle<Smi> StoreNormal(Isolate* isolate);
// Creates a Smi-handler for storing a property on a proxy.
static inline Handle<Smi> StoreProxy(Isolate* isolate);
// Creates a Smi-handler for transitioning store to a field.
static inline Handle<Smi> TransitionToField(Isolate* isolate, int descriptor,
FieldIndex field_index,
......
This diff is collapsed.
......@@ -359,6 +359,9 @@ class StoreIC : public IC {
Handle<JSObject> holder,
Handle<Map> transition, Handle<Name> name);
Handle<Object> StoreProxy(Handle<Map> receiver_map, Handle<JSProxy> proxy,
Handle<JSReceiver> receiver, Handle<Name> name);
friend class IC;
bool created_new_transition_ = false;
......
......@@ -841,7 +841,8 @@ void KeyedStoreGenericAssembler::EmitGenericPropertyStore(
BIND(&found_handler);
{
Comment("KeyedStoreGeneric found transition handler");
HandleStoreICHandlerCase(p, var_handler.value(), notfound);
HandleStoreICHandlerCase(p, var_handler.value(), notfound,
language_mode);
}
}
}
......@@ -949,7 +950,8 @@ void KeyedStoreGenericAssembler::EmitGenericPropertyStore(
BIND(&found_handler);
{
Comment("KeyedStoreGeneric found handler");
HandleStoreICHandlerCase(p, var_handler.value(), &stub_cache_miss);
HandleStoreICHandlerCase(p, var_handler.value(), &stub_cache_miss,
language_mode);
}
BIND(&stub_cache_miss);
{
......@@ -1020,13 +1022,14 @@ void KeyedStoreGenericAssembler::StoreIC_Uninitialized(
Node* vector = Parameter(Descriptor::kVector);
Node* context = Parameter(Descriptor::kContext);
Label miss(this);
Label miss(this), if_proxy(this, Label::kDeferred);
GotoIf(TaggedIsSmi(receiver), &miss);
Node* receiver_map = LoadMap(receiver);
Node* instance_type = LoadMapInstanceType(receiver_map);
GotoIf(Word32Equal(instance_type, Int32Constant(JS_PROXY_TYPE)), &if_proxy);
// Receivers requiring non-standard element accesses (interceptors, access
// checks, strings and string wrappers, proxies) are handled in the runtime.
// checks, strings and string wrappers) are handled in the runtime.
GotoIf(Int32LessThanOrEqual(instance_type,
Int32Constant(LAST_SPECIAL_RECEIVER_TYPE)),
&miss);
......@@ -1040,6 +1043,12 @@ void KeyedStoreGenericAssembler::StoreIC_Uninitialized(
EmitGenericPropertyStore(receiver, receiver_map, &p, &miss, language_mode,
kDontUseStubCache);
BIND(&if_proxy);
{
CallBuiltin(Builtins::kProxySetProperty, context, receiver, name, value,
receiver, SmiConstant(language_mode));
Return(value);
}
BIND(&miss);
{
// Undo the optimistic state transition.
......
......@@ -13,12 +13,42 @@
namespace v8 {
namespace internal {
// static
LookupIterator LookupIterator::PropertyOrElement(
Isolate* isolate, Handle<Object> receiver, Handle<Object> key,
bool* success, Handle<JSReceiver> holder, Configuration configuration) {
uint32_t index = 0;
if (key->ToArrayIndex(&index)) {
*success = true;
return LookupIterator(isolate, receiver, index, holder, configuration);
}
Handle<Name> name;
*success = Object::ToName(isolate, key).ToHandle(&name);
if (!*success) {
DCHECK(isolate->has_pending_exception());
// Return an unusable dummy.
return LookupIterator(receiver, isolate->factory()->empty_string());
}
if (name->AsArrayIndex(&index)) {
LookupIterator it(isolate, receiver, index, holder, configuration);
// Here we try to avoid having to rebuild the string later
// by storing it on the indexed LookupIterator.
it.name_ = name;
return it;
}
return LookupIterator(receiver, name, holder, configuration);
}
// static
LookupIterator LookupIterator::PropertyOrElement(Isolate* isolate,
Handle<Object> receiver,
Handle<Object> key,
bool* success,
Configuration configuration) {
// TODO(mslekova): come up with better way to avoid duplication
uint32_t index = 0;
if (key->ToArrayIndex(&index)) {
*success = true;
......
......@@ -126,6 +126,11 @@ class V8_EXPORT_PRIVATE LookupIterator final BASE_EMBEDDED {
return LookupIterator(receiver, name, holder, configuration);
}
static LookupIterator PropertyOrElement(
Isolate* isolate, Handle<Object> receiver, Handle<Object> key,
bool* success, Handle<JSReceiver> holder,
Configuration configuration = DEFAULT);
static LookupIterator PropertyOrElement(
Isolate* isolate, Handle<Object> receiver, Handle<Object> key,
bool* success, Configuration configuration = DEFAULT);
......
......@@ -1106,7 +1106,7 @@ MaybeHandle<Object> JSProxy::GetProperty(Isolate* isolate,
Execution::Call(isolate, trap, handler, arraysize(args), args), Object);
MaybeHandle<Object> result =
JSProxy::CheckGetTrapResult(isolate, name, target, trap_result);
JSProxy::CheckGetSetTrapResult(isolate, name, target, trap_result, kGet);
if (result.is_null()) {
return result;
}
......@@ -1116,10 +1116,11 @@ MaybeHandle<Object> JSProxy::GetProperty(Isolate* isolate,
}
// static
MaybeHandle<Object> JSProxy::CheckGetTrapResult(Isolate* isolate,
Handle<Name> name,
Handle<JSReceiver> target,
Handle<Object> trap_result) {
MaybeHandle<Object> JSProxy::CheckGetSetTrapResult(Isolate* isolate,
Handle<Name> name,
Handle<JSReceiver> target,
Handle<Object> trap_result,
AccessKind access_kind) {
// 9. Let targetDesc be ? target.[[GetOwnProperty]](P).
PropertyDescriptor target_desc;
Maybe<bool> target_found =
......@@ -1136,24 +1137,43 @@ MaybeHandle<Object> JSProxy::CheckGetTrapResult(Isolate* isolate,
!target_desc.writable() &&
!trap_result->SameValue(*target_desc.value());
if (inconsistent) {
THROW_NEW_ERROR(
isolate, NewTypeError(MessageTemplate::kProxyGetNonConfigurableData,
name, target_desc.value(), trap_result),
Object);
if (access_kind == kGet) {
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kProxyGetNonConfigurableData, name,
target_desc.value(), trap_result),
Object);
} else {
isolate->Throw(*isolate->factory()->NewTypeError(
MessageTemplate::kProxySetFrozenData, name));
return MaybeHandle<Object>();
}
}
// 10.b. If IsAccessorDescriptor(targetDesc) and targetDesc.[[Configurable]]
// is false and targetDesc.[[Get]] is undefined, then
// 10.b.i. If trapResult is not undefined, throw a TypeError exception.
inconsistent = PropertyDescriptor::IsAccessorDescriptor(&target_desc) &&
!target_desc.configurable() &&
target_desc.get()->IsUndefined(isolate) &&
!trap_result->IsUndefined(isolate);
if (access_kind == kGet) {
inconsistent = PropertyDescriptor::IsAccessorDescriptor(&target_desc) &&
!target_desc.configurable() &&
target_desc.get()->IsUndefined(isolate) &&
!trap_result->IsUndefined(isolate);
} else {
inconsistent = PropertyDescriptor::IsAccessorDescriptor(&target_desc) &&
!target_desc.configurable() &&
target_desc.set()->IsUndefined(isolate);
}
if (inconsistent) {
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kProxyGetNonConfigurableAccessor, name,
trap_result),
Object);
if (access_kind == kGet) {
THROW_NEW_ERROR(
isolate,
NewTypeError(MessageTemplate::kProxyGetNonConfigurableAccessor,
name, trap_result),
Object);
} else {
isolate->Throw(*isolate->factory()->NewTypeError(
MessageTemplate::kProxySetFrozenAccessor, name));
return MaybeHandle<Object>();
}
}
}
return isolate->factory()->undefined_value();
......@@ -5527,29 +5547,11 @@ Maybe<bool> JSProxy::SetProperty(Handle<JSProxy> proxy, Handle<Name> name,
trap_name, name));
}
// Enforce the invariant.
PropertyDescriptor target_desc;
Maybe<bool> owned =
JSReceiver::GetOwnPropertyDescriptor(isolate, target, name, &target_desc);
MAYBE_RETURN(owned, Nothing<bool>());
if (owned.FromJust()) {
bool inconsistent = PropertyDescriptor::IsDataDescriptor(&target_desc) &&
!target_desc.configurable() &&
!target_desc.writable() &&
!value->SameValue(*target_desc.value());
if (inconsistent) {
isolate->Throw(*isolate->factory()->NewTypeError(
MessageTemplate::kProxySetFrozenData, name));
return Nothing<bool>();
}
inconsistent = PropertyDescriptor::IsAccessorDescriptor(&target_desc) &&
!target_desc.configurable() &&
target_desc.set()->IsUndefined(isolate);
if (inconsistent) {
isolate->Throw(*isolate->factory()->NewTypeError(
MessageTemplate::kProxySetFrozenAccessor, name));
return Nothing<bool>();
}
MaybeHandle<Object> result =
JSProxy::CheckGetSetTrapResult(isolate, name, target, value, kSet);
if (result.is_null()) {
return Nothing<bool>();
}
return Just(true);
}
......
......@@ -6046,10 +6046,13 @@ class JSProxy: public JSReceiver {
Isolate* isolate, Handle<JSProxy> proxy, Handle<Name> name,
Handle<Object> receiver, bool* was_found);
static MaybeHandle<Object> CheckGetTrapResult(Isolate* isolate,
Handle<Name> name,
Handle<JSReceiver> target,
Handle<Object> trap_result);
enum AccessKind { kGet, kSet };
static MaybeHandle<Object> CheckGetSetTrapResult(Isolate* isolate,
Handle<Name> name,
Handle<JSReceiver> target,
Handle<Object> trap_result,
AccessKind access_kind);
// ES6 9.5.9
MUST_USE_RESULT static Maybe<bool> SetProperty(Handle<JSProxy> proxy,
......
......@@ -51,24 +51,47 @@ RUNTIME_FUNCTION(Runtime_GetPropertyWithReceiver) {
DCHECK_EQ(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, holder, 0);
CONVERT_ARG_HANDLE_CHECKED(Name, name, 1);
CONVERT_ARG_HANDLE_CHECKED(Object, name, 1);
CONVERT_ARG_HANDLE_CHECKED(Object, receiver, 2);
LookupIterator it =
LookupIterator::PropertyOrElement(isolate, receiver, name, holder);
bool success;
LookupIterator it = LookupIterator::PropertyOrElement(isolate, receiver, name,
&success, holder);
RETURN_RESULT_OR_FAILURE(isolate, Object::GetProperty(&it));
}
RUNTIME_FUNCTION(Runtime_CheckProxyGetTrapResult) {
RUNTIME_FUNCTION(Runtime_SetPropertyWithReceiver) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
DCHECK_EQ(5, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, holder, 0);
CONVERT_ARG_HANDLE_CHECKED(Object, name, 1);
CONVERT_ARG_HANDLE_CHECKED(Object, value, 2);
CONVERT_ARG_HANDLE_CHECKED(Object, receiver, 3);
CONVERT_LANGUAGE_MODE_ARG_CHECKED(language_mode, 4);
bool success;
LookupIterator it = LookupIterator::PropertyOrElement(isolate, receiver, name,
&success, holder);
Maybe<bool> result = Object::SetSuperProperty(
&it, value, language_mode, Object::MAY_BE_STORE_FROM_KEYED);
MAYBE_RETURN(result, isolate->heap()->exception());
return *isolate->factory()->ToBoolean(result.FromJust());
}
RUNTIME_FUNCTION(Runtime_CheckProxyGetSetTrapResult) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
CONVERT_ARG_HANDLE_CHECKED(Name, name, 0);
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, target, 1);
CONVERT_ARG_HANDLE_CHECKED(Object, trap_result, 2);
CONVERT_NUMBER_CHECKED(int64_t, access_kind, Int64, args[3]);
RETURN_RESULT_OR_FAILURE(
isolate, JSProxy::CheckGetTrapResult(isolate, name, target, trap_result));
RETURN_RESULT_OR_FAILURE(isolate, JSProxy::CheckGetSetTrapResult(
isolate, name, target, trap_result,
JSProxy::AccessKind(access_kind)));
}
RUNTIME_FUNCTION(Runtime_CheckProxyHasTrap) {
......
......@@ -468,8 +468,9 @@ namespace internal {
F(JSProxyGetHandler, 1, 1) \
F(JSProxyRevoke, 1, 1) \
F(GetPropertyWithReceiver, 2, 1) \
F(CheckProxyGetTrapResult, 2, 1) \
F(CheckProxyHasTrap, 2, 1)
F(CheckProxyHasTrap, 2, 1) \
F(SetPropertyWithReceiver, 5, 1) \
F(CheckProxyGetSetTrapResult, 2, 1)
#define FOR_EACH_INTRINSIC_REGEXP(F) \
F(IsRegExp, 1, 1) \
......
......@@ -206,8 +206,6 @@
'builtins/builtins-promise-gen.h',
'builtins/builtins-proxy-gen.cc',
'builtins/builtins-proxy-gen.h',
'builtins/builtins-proxy-helpers-gen.cc',
'builtins/builtins-proxy-helpers-gen.h',
'builtins/builtins-regexp-gen.cc',
'builtins/builtins-regexp-gen.h',
'builtins/builtins-sharedarraybuffer-gen.cc',
......
......@@ -193,7 +193,6 @@
'Error from proxy getOwnPropertyDescriptor trap');
})();
(function testGetPropertyDetailsBailout2() {
var obj = {};
Object.defineProperty(obj, 'prop', {
......@@ -211,3 +210,13 @@
" property on the proxy target but the proxy did not return its actual" +
" value (expected '53' but got '42')");
})();
(function test32BitIndex() {
var index = (1 << 31) + 1;
var obj = {};
obj[index] = 42;
var p = new Proxy(obj, {});
for (var i = 0; i < 3; ++i) {
assertEquals(42, p[index]);
}
})();
......@@ -308,3 +308,101 @@ TestTrapReceiverArgument(strictReflectSet);
}
}
})();
function TestTargetProxy(mySet) {
var q = new Proxy({}, {});
var proxy = new Proxy(q, {
set: function(t, k, v) {
return Reflect.set(t, k, v);
}
});
for (var p of properties) {
assertTrueIf(mySet.returnsBool, mySet(proxy, p, 42));
assertSame(42, q[p]);
}
};
TestTargetProxy(sloppyDefaultSet);
TestTargetProxy(sloppyReflectSet);
TestTargetProxy(strictDefaultSet);
TestTargetProxy(strictReflectSet);
(function TestAccessorNoSet() {
var target = {
};
Object.defineProperty(target, 'prop', {
get: function() {
return 42;
},
configurable: false
})
var handler = {
set: function() { return true; }
}
var proxy = new Proxy(target, handler);
assertThrows(function() { proxy.prop = 0; }, TypeError);
})();
(function TestProxyInPrototype() {
var handler = {
set: function(t, k, v) {
Reflect.set(t, k, v);
}
};
var obj = {};
var proxy = new Proxy(obj, handler);
var o = Object.create(proxy);
for (var i = 0; i < 3; ++i) {
o.prop = 42 + i;
assertEquals(42 + i, obj.prop);
}
})();
(function TestProxyInPrototypeNoTrap() {
var handler = {
};
var obj = {};
var proxy = new Proxy(obj, handler);
var o = Object.create(proxy);
for (var i = 0; i < 3; ++i) {
o.prop = 42 + i;
assertEquals(42 + i, o.prop);
assertEquals(undefined, obj.prop);
}
})();
// Note: this case is currently handled by runtime.
(function TestDifferentHolder() {
var obj = {
'1337': 100
};
var handler = {
set(target, name, value, receiver) {
if (name != '1337') return Reflect.set(target, name, value, receiver);
assertSame(target, obj);
assertSame(receiver, p);
return target[name] = value;
}
};
var p = new Proxy(obj, handler);
for (var i = 0; i < 3; ++i) {
assertEquals(42, p[1337] = 42);
}
})();
(function test32BitIndex() {
var index = (1 << 31) + 1;
var obj = {};
obj[index] = 42;
var p = new Proxy(obj, {});
for (var i = 0; i < 3; ++i) {
p[index] = 100;
assertEquals(100, obj[index]);
}
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment