Add Context::DeepFreeze
Change-Id: I1002944931fa7705048457e2cd2c39494923c750 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3691125 Reviewed-by: Camillo Bruni <cbruni@chromium.org> Commit-Queue: Russ Hamilton <behamilton@google.com> Reviewed-by: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/main@{#85710}
This commit is contained in:
parent
d367ee2ac6
commit
2833957c77
@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "v8-data.h" // NOLINT(build/include_directory)
|
#include "v8-data.h" // NOLINT(build/include_directory)
|
||||||
#include "v8-local-handle.h" // NOLINT(build/include_directory)
|
#include "v8-local-handle.h" // NOLINT(build/include_directory)
|
||||||
|
#include "v8-maybe.h" // NOLINT(build/include_directory)
|
||||||
#include "v8-snapshot.h" // NOLINT(build/include_directory)
|
#include "v8-snapshot.h" // NOLINT(build/include_directory)
|
||||||
#include "v8config.h" // NOLINT(build/include_directory)
|
#include "v8config.h" // NOLINT(build/include_directory)
|
||||||
|
|
||||||
@ -163,6 +164,13 @@ class V8_EXPORT Context : public Data {
|
|||||||
*/
|
*/
|
||||||
void Exit();
|
void Exit();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to recursively freeze all objects reachable from this context.
|
||||||
|
* Some objects (generators, iterators, non-const closures) can not be frozen
|
||||||
|
* and will cause this method to throw an error.
|
||||||
|
*/
|
||||||
|
Maybe<void> DeepFreeze();
|
||||||
|
|
||||||
/** Returns the isolate associated with a current context. */
|
/** Returns the isolate associated with a current context. */
|
||||||
Isolate* GetIsolate();
|
Isolate* GetIsolate();
|
||||||
|
|
||||||
|
214
src/api/api.cc
214
src/api/api.cc
@ -47,6 +47,7 @@
|
|||||||
#include "src/compiler-dispatcher/lazy-compile-dispatcher.h"
|
#include "src/compiler-dispatcher/lazy-compile-dispatcher.h"
|
||||||
#include "src/date/date.h"
|
#include "src/date/date.h"
|
||||||
#include "src/objects/primitive-heap-object.h"
|
#include "src/objects/primitive-heap-object.h"
|
||||||
|
#include "src/utils/identity-map.h"
|
||||||
#if V8_ENABLE_WEBASSEMBLY
|
#if V8_ENABLE_WEBASSEMBLY
|
||||||
#include "src/debug/debug-wasm-objects.h"
|
#include "src/debug/debug-wasm-objects.h"
|
||||||
#endif // V8_ENABLE_WEBASSEMBLY
|
#endif // V8_ENABLE_WEBASSEMBLY
|
||||||
@ -85,6 +86,7 @@
|
|||||||
#include "src/objects/embedder-data-slot-inl.h"
|
#include "src/objects/embedder-data-slot-inl.h"
|
||||||
#include "src/objects/hash-table-inl.h"
|
#include "src/objects/hash-table-inl.h"
|
||||||
#include "src/objects/heap-object.h"
|
#include "src/objects/heap-object.h"
|
||||||
|
#include "src/objects/instance-type.h"
|
||||||
#include "src/objects/js-array-buffer-inl.h"
|
#include "src/objects/js-array-buffer-inl.h"
|
||||||
#include "src/objects/js-array-inl.h"
|
#include "src/objects/js-array-inl.h"
|
||||||
#include "src/objects/js-collection-inl.h"
|
#include "src/objects/js-collection-inl.h"
|
||||||
@ -6717,6 +6719,218 @@ Local<Value> v8::Context::GetSecurityToken() {
|
|||||||
return Utils::ToLocal(token_handle);
|
return Utils::ToLocal(token_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool MayContainObjectsToFreeze(i::InstanceType obj_type) {
|
||||||
|
if (i::InstanceTypeChecker::IsString(obj_type)) return false;
|
||||||
|
if (i::InstanceTypeChecker::IsSharedFunctionInfo(obj_type)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsJSReceiverSafeToFreeze(i::InstanceType obj_type) {
|
||||||
|
DCHECK(i::InstanceTypeChecker::IsJSReceiver(obj_type));
|
||||||
|
switch (obj_type) {
|
||||||
|
case i::JS_OBJECT_TYPE:
|
||||||
|
case i::JS_GLOBAL_OBJECT_TYPE:
|
||||||
|
case i::JS_GLOBAL_PROXY_TYPE:
|
||||||
|
case i::JS_PRIMITIVE_WRAPPER_TYPE:
|
||||||
|
case i::JS_FUNCTION_TYPE:
|
||||||
|
/* Function types */
|
||||||
|
case i::BIGINT64_TYPED_ARRAY_CONSTRUCTOR_TYPE:
|
||||||
|
case i::BIGUINT64_TYPED_ARRAY_CONSTRUCTOR_TYPE:
|
||||||
|
case i::FLOAT32_TYPED_ARRAY_CONSTRUCTOR_TYPE:
|
||||||
|
case i::FLOAT64_TYPED_ARRAY_CONSTRUCTOR_TYPE:
|
||||||
|
case i::INT16_TYPED_ARRAY_CONSTRUCTOR_TYPE:
|
||||||
|
case i::INT32_TYPED_ARRAY_CONSTRUCTOR_TYPE:
|
||||||
|
case i::INT8_TYPED_ARRAY_CONSTRUCTOR_TYPE:
|
||||||
|
case i::UINT16_TYPED_ARRAY_CONSTRUCTOR_TYPE:
|
||||||
|
case i::UINT32_TYPED_ARRAY_CONSTRUCTOR_TYPE:
|
||||||
|
case i::UINT8_CLAMPED_TYPED_ARRAY_CONSTRUCTOR_TYPE:
|
||||||
|
case i::UINT8_TYPED_ARRAY_CONSTRUCTOR_TYPE:
|
||||||
|
case i::JS_ARRAY_CONSTRUCTOR_TYPE:
|
||||||
|
case i::JS_PROMISE_CONSTRUCTOR_TYPE:
|
||||||
|
case i::JS_REG_EXP_CONSTRUCTOR_TYPE:
|
||||||
|
case i::JS_CLASS_CONSTRUCTOR_TYPE:
|
||||||
|
/* Prototype Types */
|
||||||
|
case i::JS_ARRAY_ITERATOR_PROTOTYPE_TYPE:
|
||||||
|
case i::JS_ITERATOR_PROTOTYPE_TYPE:
|
||||||
|
case i::JS_MAP_ITERATOR_PROTOTYPE_TYPE:
|
||||||
|
case i::JS_OBJECT_PROTOTYPE_TYPE:
|
||||||
|
case i::JS_PROMISE_PROTOTYPE_TYPE:
|
||||||
|
case i::JS_REG_EXP_PROTOTYPE_TYPE:
|
||||||
|
case i::JS_SET_ITERATOR_PROTOTYPE_TYPE:
|
||||||
|
case i::JS_SET_PROTOTYPE_TYPE:
|
||||||
|
case i::JS_STRING_ITERATOR_PROTOTYPE_TYPE:
|
||||||
|
case i::JS_TYPED_ARRAY_PROTOTYPE_TYPE:
|
||||||
|
/* */
|
||||||
|
case i::JS_ARRAY_TYPE:
|
||||||
|
return true;
|
||||||
|
#if V8_ENABLE_WEBASSEMBLY
|
||||||
|
case i::WASM_ARRAY_TYPE:
|
||||||
|
case i::WASM_STRUCT_TYPE:
|
||||||
|
#endif // V8_ENABLE_WEBASSEMBLY
|
||||||
|
case i::JS_PROXY_TYPE:
|
||||||
|
return true;
|
||||||
|
// These types are known not to freeze.
|
||||||
|
case i::JS_MAP_KEY_ITERATOR_TYPE:
|
||||||
|
case i::JS_MAP_KEY_VALUE_ITERATOR_TYPE:
|
||||||
|
case i::JS_MAP_VALUE_ITERATOR_TYPE:
|
||||||
|
case i::JS_SET_KEY_VALUE_ITERATOR_TYPE:
|
||||||
|
case i::JS_SET_VALUE_ITERATOR_TYPE:
|
||||||
|
case i::JS_GENERATOR_OBJECT_TYPE:
|
||||||
|
case i::JS_ASYNC_FUNCTION_OBJECT_TYPE:
|
||||||
|
case i::JS_ASYNC_GENERATOR_OBJECT_TYPE:
|
||||||
|
case i::JS_ARRAY_ITERATOR_TYPE: {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO(behamilton): Handle any types that fall through here.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObjectVisitorDeepFreezer : i::ObjectVisitor {
|
||||||
|
public:
|
||||||
|
explicit ObjectVisitorDeepFreezer(i::Isolate* isolate) : isolate_(isolate) {}
|
||||||
|
|
||||||
|
bool DeepFreeze(i::Handle<i::Context> context) {
|
||||||
|
bool success = VisitObject(*i::Handle<i::HeapObject>::cast(context));
|
||||||
|
DCHECK_EQ(success, !error_.has_value());
|
||||||
|
if (!success) {
|
||||||
|
THROW_NEW_ERROR_RETURN_VALUE(
|
||||||
|
isolate_, NewTypeError(error_->msg_id, error_->name), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& obj : objects_to_freeze_) {
|
||||||
|
MAYBE_RETURN_ON_EXCEPTION_VALUE(
|
||||||
|
isolate_,
|
||||||
|
i::JSReceiver::SetIntegrityLevel(isolate_, obj, i::FROZEN,
|
||||||
|
i::kThrowOnError),
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VisitPointers(i::HeapObject host, i::ObjectSlot start,
|
||||||
|
i::ObjectSlot end) final {
|
||||||
|
VisitPointersImpl(start, end);
|
||||||
|
}
|
||||||
|
void VisitPointers(i::HeapObject host, i::MaybeObjectSlot start,
|
||||||
|
i::MaybeObjectSlot end) final {
|
||||||
|
VisitPointersImpl(start, end);
|
||||||
|
}
|
||||||
|
void VisitMapPointer(i::HeapObject host) final {
|
||||||
|
VisitPointer(host, host.map_slot());
|
||||||
|
}
|
||||||
|
void VisitCodePointer(i::HeapObject host, i::CodeObjectSlot slot) final {}
|
||||||
|
void VisitCodeTarget(i::InstructionStream host, i::RelocInfo* rinfo) final {}
|
||||||
|
void VisitEmbeddedPointer(i::InstructionStream host,
|
||||||
|
i::RelocInfo* rinfo) final {}
|
||||||
|
void VisitCustomWeakPointers(i::HeapObject host, i::ObjectSlot start,
|
||||||
|
i::ObjectSlot end) final {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct ErrorInfo {
|
||||||
|
i::MessageTemplate msg_id;
|
||||||
|
i::Handle<i::String> name;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename TSlot>
|
||||||
|
void VisitPointersImpl(TSlot start, TSlot end) {
|
||||||
|
for (TSlot current = start; current < end; ++current) {
|
||||||
|
typename TSlot::TObject object = current.load(isolate_);
|
||||||
|
i::HeapObject heap_object;
|
||||||
|
if (object.GetHeapObjectIfStrong(&heap_object)) {
|
||||||
|
if (!VisitObject(heap_object)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VisitObject(i::HeapObject obj) {
|
||||||
|
DCHECK(!error_.has_value());
|
||||||
|
DCHECK(!obj.is_null());
|
||||||
|
|
||||||
|
i::DisallowGarbageCollection no_gc;
|
||||||
|
i::InstanceType obj_type = obj.map().instance_type();
|
||||||
|
|
||||||
|
// Skip common types that can't contain items to freeze.
|
||||||
|
if (!MayContainObjectsToFreeze(obj_type)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!done_list_.insert(obj).second) {
|
||||||
|
// If we couldn't insert (because it is already in the set) then we're
|
||||||
|
// done.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For contexts we need to ensure that all accessible locals are const.
|
||||||
|
// If not they could be replaced to bypass freezing.
|
||||||
|
if (i::InstanceTypeChecker::IsContext(obj_type)) {
|
||||||
|
i::ScopeInfo scope_info = i::Context::cast(obj).scope_info();
|
||||||
|
for (auto it : i::ScopeInfo::IterateLocalNames(&scope_info, no_gc)) {
|
||||||
|
if (scope_info.ContextLocalMode(it->index()) !=
|
||||||
|
i::VariableMode::kConst) {
|
||||||
|
DCHECK(!error_.has_value());
|
||||||
|
error_ = ErrorInfo{i::MessageTemplate::kCannotDeepFreezeValue,
|
||||||
|
i::handle(it->name(), isolate_)};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (i::InstanceTypeChecker::IsJSReceiver(obj_type)) {
|
||||||
|
i::Handle<i::JSReceiver> receiver =
|
||||||
|
i::handle(i::JSReceiver::cast(obj), isolate_);
|
||||||
|
if (!IsJSReceiverSafeToFreeze(obj_type)) {
|
||||||
|
DCHECK(!error_.has_value());
|
||||||
|
error_ = ErrorInfo{i::MessageTemplate::kCannotDeepFreezeObject,
|
||||||
|
i::handle(receiver->class_name(), isolate_)};
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save this to freeze after we are done. Freezing triggers garbage
|
||||||
|
// collection which doesn't work well with this visitor pattern, so we
|
||||||
|
// delay it until after.
|
||||||
|
objects_to_freeze_.push_back(receiver);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
DCHECK(!i::InstanceTypeChecker::IsContext(obj_type) &&
|
||||||
|
!i::InstanceTypeChecker::IsJSReceiver(obj_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
DCHECK(!error_.has_value());
|
||||||
|
obj.Iterate(isolate_, this);
|
||||||
|
// Iterate sets error_ on failure. We should propagate errors.
|
||||||
|
return !error_.has_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
i::Isolate* isolate_;
|
||||||
|
std::unordered_set<i::Object, i::Object::Hasher> done_list_;
|
||||||
|
std::vector<i::Handle<i::JSReceiver>> objects_to_freeze_;
|
||||||
|
base::Optional<ErrorInfo> error_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Maybe<void> Context::DeepFreeze() {
|
||||||
|
i::Handle<i::Context> env = Utils::OpenHandle(this);
|
||||||
|
i::Isolate* i_isolate = env->GetIsolate();
|
||||||
|
|
||||||
|
// TODO(behamilton): Incorporate compatibility improvements similar to NodeJS:
|
||||||
|
// https://github.com/nodejs/node/blob/main/lib/internal/freeze_intrinsics.js
|
||||||
|
// These need to be done before freezing.
|
||||||
|
|
||||||
|
Local<Context> context = Utils::ToLocal(env);
|
||||||
|
ENTER_V8_NO_SCRIPT(i_isolate, context, Context, DeepFreeze, Nothing<void>(),
|
||||||
|
i::HandleScope);
|
||||||
|
ObjectVisitorDeepFreezer vfreezer(i_isolate);
|
||||||
|
has_pending_exception = !vfreezer.DeepFreeze(env);
|
||||||
|
|
||||||
|
RETURN_ON_FAILED_EXECUTION_PRIMITIVE(void);
|
||||||
|
return JustVoid();
|
||||||
|
}
|
||||||
|
|
||||||
v8::Isolate* Context::GetIsolate() {
|
v8::Isolate* Context::GetIsolate() {
|
||||||
i::Handle<i::Context> env = Utils::OpenHandle(this);
|
i::Handle<i::Context> env = Utils::OpenHandle(this);
|
||||||
return reinterpret_cast<Isolate*>(env->GetIsolate());
|
return reinterpret_cast<Isolate*>(env->GetIsolate());
|
||||||
|
@ -711,7 +711,10 @@ namespace internal {
|
|||||||
T(OptionalChainingNoSuper, "Invalid optional chain from super property") \
|
T(OptionalChainingNoSuper, "Invalid optional chain from super property") \
|
||||||
T(OptionalChainingNoTemplate, "Invalid tagged template on optional chain") \
|
T(OptionalChainingNoTemplate, "Invalid tagged template on optional chain") \
|
||||||
/* AggregateError */ \
|
/* AggregateError */ \
|
||||||
T(AllPromisesRejected, "All promises were rejected")
|
T(AllPromisesRejected, "All promises were rejected") \
|
||||||
|
T(CannotDeepFreezeObject, "Cannot DeepFreeze object of type %") \
|
||||||
|
T(CannotDeepFreezeValue, "Cannot DeepFreeze non-const value %")
|
||||||
|
|
||||||
enum class MessageTemplate {
|
enum class MessageTemplate {
|
||||||
#define TEMPLATE(NAME, STRING) k##NAME,
|
#define TEMPLATE(NAME, STRING) k##NAME,
|
||||||
MESSAGE_TEMPLATES(TEMPLATE)
|
MESSAGE_TEMPLATES(TEMPLATE)
|
||||||
|
@ -144,6 +144,7 @@ class RuntimeCallTimer final {
|
|||||||
V(BigUint64Array_New) \
|
V(BigUint64Array_New) \
|
||||||
V(BooleanObject_BooleanValue) \
|
V(BooleanObject_BooleanValue) \
|
||||||
V(BooleanObject_New) \
|
V(BooleanObject_New) \
|
||||||
|
V(Context_DeepFreeze) \
|
||||||
V(Context_New) \
|
V(Context_New) \
|
||||||
V(Context_NewRemoteContext) \
|
V(Context_NewRemoteContext) \
|
||||||
V(DataView_New) \
|
V(DataView_New) \
|
||||||
|
@ -29553,3 +29553,265 @@ TEST(WasmAbortStreamingAfterContextDisposal) {
|
|||||||
wasm_streaming.reset();
|
wasm_streaming.reset();
|
||||||
}
|
}
|
||||||
#endif // V8_ENABLE_WEBASSEMBLY
|
#endif // V8_ENABLE_WEBASSEMBLY
|
||||||
|
|
||||||
|
TEST(DeepFreezeIncompatibleTypes) {
|
||||||
|
const int numCases = 7;
|
||||||
|
struct {
|
||||||
|
const char* script;
|
||||||
|
const char* exception;
|
||||||
|
} test_cases[numCases] = {
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
"use strict"
|
||||||
|
let foo = 1;
|
||||||
|
)",
|
||||||
|
"TypeError: Cannot DeepFreeze non-const value foo"},
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
"use strict"
|
||||||
|
const foo = 1;
|
||||||
|
const generator = function*() {
|
||||||
|
yield 1;
|
||||||
|
yield 2;
|
||||||
|
}
|
||||||
|
const gen = generator();
|
||||||
|
)",
|
||||||
|
"TypeError: Cannot DeepFreeze object of type Generator"},
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
"use strict"
|
||||||
|
const incrementer = (function() {
|
||||||
|
let a = 1;
|
||||||
|
return function() { a += 1; return a; };
|
||||||
|
})();
|
||||||
|
)",
|
||||||
|
"TypeError: Cannot DeepFreeze non-const value a"},
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
let a = new Number();
|
||||||
|
)",
|
||||||
|
"TypeError: Cannot DeepFreeze non-const value a"},
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
const a = [0, 1, 2, 3, 4, 5];
|
||||||
|
var it = a[Symbol.iterator]();
|
||||||
|
function foo() {
|
||||||
|
return it.next().value;
|
||||||
|
}
|
||||||
|
foo();
|
||||||
|
)",
|
||||||
|
"TypeError: Cannot DeepFreeze object of type Array Iterator"},
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
const a = "0123456789";
|
||||||
|
var it = a[Symbol.iterator]();
|
||||||
|
function foo() {
|
||||||
|
return it.next().value;
|
||||||
|
}
|
||||||
|
foo();
|
||||||
|
)",
|
||||||
|
"TypeError: Cannot DeepFreeze object of type Object"},
|
||||||
|
{R"(
|
||||||
|
const a = "0123456789";
|
||||||
|
var it = a.matchAll(/\d/g);
|
||||||
|
function foo() {
|
||||||
|
return it.next().value;
|
||||||
|
}
|
||||||
|
foo();
|
||||||
|
)",
|
||||||
|
"TypeError: Cannot DeepFreeze object of type Object"},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int idx = 0; idx < numCases; idx++) {
|
||||||
|
LocalContext env;
|
||||||
|
v8::Isolate* isolate = env->GetIsolate();
|
||||||
|
v8::HandleScope scope(isolate);
|
||||||
|
v8::Local<v8::Context> context = env.local();
|
||||||
|
v8::Maybe<void> maybe_success = v8::Nothing<void>();
|
||||||
|
CompileRun(context, test_cases[idx].script);
|
||||||
|
v8::TryCatch tc(isolate);
|
||||||
|
maybe_success = context->DeepFreeze();
|
||||||
|
CHECK(maybe_success.IsNothing());
|
||||||
|
CHECK(tc.HasCaught());
|
||||||
|
v8::String::Utf8Value uS(isolate, tc.Exception());
|
||||||
|
std::string exception(*uS, uS.length());
|
||||||
|
CHECK_EQ(std::string(test_cases[idx].exception), exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DeepFreezeIsFrozen) {
|
||||||
|
const int numCases = 10;
|
||||||
|
struct {
|
||||||
|
const char* script;
|
||||||
|
const char* exception;
|
||||||
|
int32_t expected;
|
||||||
|
} test_cases[numCases] = {
|
||||||
|
{// Closure
|
||||||
|
R"(
|
||||||
|
const incrementer = (function() {
|
||||||
|
const a = {b: 1};
|
||||||
|
return function() { a.b += 1; return a.b; };
|
||||||
|
})();
|
||||||
|
const foo = function() { return incrementer(); }
|
||||||
|
foo();
|
||||||
|
)",
|
||||||
|
nullptr, 2},
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
const incrementer = (function() {
|
||||||
|
const a = {b: 1};
|
||||||
|
return function() { a.b += 1; return a.b; };
|
||||||
|
})();
|
||||||
|
const foo = function() { return incrementer(); }
|
||||||
|
foo();
|
||||||
|
)",
|
||||||
|
nullptr, 2},
|
||||||
|
{// Array
|
||||||
|
R"(
|
||||||
|
const a = [0, -1, -2];
|
||||||
|
const foo = function() { a[0] += 1; return a[0]; }
|
||||||
|
)",
|
||||||
|
nullptr, 0},
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
const a = [0, -1, -2];
|
||||||
|
const foo = function() { a[0] += 1; return a[0]; }
|
||||||
|
)",
|
||||||
|
nullptr, 0},
|
||||||
|
{// Wrapper Objects
|
||||||
|
R"(
|
||||||
|
const a = {b: new Number()};
|
||||||
|
const foo = function() {
|
||||||
|
a.b = new Number(a.b + 1);
|
||||||
|
return a.b.valueOf();
|
||||||
|
}
|
||||||
|
)",
|
||||||
|
nullptr, 0},
|
||||||
|
{// Functions
|
||||||
|
// Assignment to constant doesn't work.
|
||||||
|
R"(
|
||||||
|
const foo = function() {
|
||||||
|
foo = function() { return 2;}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
)",
|
||||||
|
"TypeError: Assignment to constant variable.", 0},
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
const a = {b: {c: {d: {e: {f: 1}}}}};
|
||||||
|
const foo = function() {
|
||||||
|
a.b.c.d.e.f += 1;
|
||||||
|
return a.b.c.d.e.f;
|
||||||
|
}
|
||||||
|
)",
|
||||||
|
nullptr, 1},
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
const foo = function() {
|
||||||
|
if (!('count' in globalThis))
|
||||||
|
globalThis.count = 1;
|
||||||
|
++count;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
)",
|
||||||
|
"ReferenceError: count is not defined", 0},
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
const countPrototype = {
|
||||||
|
get() {
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const count = Object.create(countPrototype);
|
||||||
|
function foo() {
|
||||||
|
const curr_count = count.get();
|
||||||
|
count.prototype = { get() { return curr_count + 1; }};
|
||||||
|
return count.get();
|
||||||
|
}
|
||||||
|
)",
|
||||||
|
nullptr, 1},
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
const a = (function(){
|
||||||
|
function A(){};
|
||||||
|
A.o = 1;
|
||||||
|
return new A();
|
||||||
|
})();
|
||||||
|
function foo() {
|
||||||
|
a.constructor.o++;
|
||||||
|
return a.constructor.o;
|
||||||
|
}
|
||||||
|
)",
|
||||||
|
nullptr, 1},
|
||||||
|
};
|
||||||
|
for (int idx = 0; idx < numCases; idx++) {
|
||||||
|
LocalContext env;
|
||||||
|
v8::Isolate* isolate = env->GetIsolate();
|
||||||
|
v8::HandleScope scope(isolate);
|
||||||
|
v8::Local<v8::Context> context = env.local();
|
||||||
|
v8::Maybe<void> maybe_success = v8::Nothing<void>();
|
||||||
|
v8::TryCatch tc(isolate);
|
||||||
|
v8::MaybeLocal<v8::Value> status =
|
||||||
|
CompileRun(context, test_cases[idx].script);
|
||||||
|
CHECK(!status.IsEmpty());
|
||||||
|
CHECK(!tc.HasCaught());
|
||||||
|
|
||||||
|
maybe_success = context->DeepFreeze();
|
||||||
|
CHECK(!tc.HasCaught());
|
||||||
|
status = CompileRun(context, "foo()");
|
||||||
|
|
||||||
|
if (test_cases[idx].exception) {
|
||||||
|
CHECK(tc.HasCaught());
|
||||||
|
v8::String::Utf8Value uS(isolate, tc.Exception());
|
||||||
|
std::string exception(*uS, uS.length());
|
||||||
|
CHECK_EQ(std::string(test_cases[idx].exception), exception);
|
||||||
|
} else {
|
||||||
|
CHECK(!tc.HasCaught());
|
||||||
|
CHECK(!status.IsEmpty());
|
||||||
|
ExpectInt32("foo()", test_cases[idx].expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(DeepFreezeAllowsSyntax) {
|
||||||
|
const int numCases = 2;
|
||||||
|
struct {
|
||||||
|
const char* script;
|
||||||
|
int32_t expected;
|
||||||
|
} test_cases[numCases] = {
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
const a = 1;
|
||||||
|
function foo() {
|
||||||
|
let b = 4;
|
||||||
|
b += 1;
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
)",
|
||||||
|
6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
R"(
|
||||||
|
var a = 1;
|
||||||
|
function foo() {
|
||||||
|
let b = 4;
|
||||||
|
b += 1;
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
)",
|
||||||
|
6,
|
||||||
|
}}; // TODO(behamilton): Add more cases that should be supported.
|
||||||
|
for (int idx = 0; idx < numCases; idx++) {
|
||||||
|
LocalContext env;
|
||||||
|
v8::Isolate* isolate = env->GetIsolate();
|
||||||
|
v8::HandleScope scope(isolate);
|
||||||
|
v8::Local<v8::Context> context = env.local();
|
||||||
|
v8::Maybe<void> maybe_success = v8::Nothing<void>();
|
||||||
|
v8::MaybeLocal<v8::Value> status =
|
||||||
|
CompileRun(context, test_cases[idx].script);
|
||||||
|
CHECK(!status.IsEmpty());
|
||||||
|
maybe_success = context->DeepFreeze();
|
||||||
|
CHECK(!maybe_success.IsNothing());
|
||||||
|
ExpectInt32("foo()", test_cases[idx].expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user