Implement immutable prototype chains

This patch implements "immutable prototype exotic objects" from the ECMAScript
spec, which are objects whose __proto__ cannot be changed, but are not otherwise
frozen. They are introduced in order to prevent a Proxy from being introduced
to the prototype chain of the global object.

The API is extended by a SetImmutablePrototype() call in ObjectTemplate, which
can be used to vend new immutable prototype objects. Additionally, Object.prototype
is an immutable prototype object.

In the implementation, a new bit is added to Maps to say whether the prototype is
immutable, which is read by SetPrototype. Map transitions to the immutable prototype
state are not saved in the transition tree because the main use case is just for
the prototype chain of the global object, which there will be only one of per
Context, so no need to take up the extra word for a pointer in each full transition
tree.

BUG=v8:5149

Review-Url: https://codereview.chromium.org/2108203002
Cr-Commit-Position: refs/heads/master@{#37482}
This commit is contained in:
littledan 2016-07-01 12:16:53 -07:00 committed by Commit bot
parent e65035f257
commit 0ff7b4830c
16 changed files with 152 additions and 27 deletions

View File

@ -4833,6 +4833,17 @@ class V8_EXPORT ObjectTemplate : public Template {
*/
void SetInternalFieldCount(int value);
/**
* Returns true if the object will be an immutable prototype exotic object.
*/
bool IsImmutableProto();
/**
* Makes the ObjectTempate for an immutable prototype exotic object, with an
* immutable __proto__.
*/
void SetImmutableProto();
private:
ObjectTemplate();
static Local<ObjectTemplate> New(internal::Isolate* isolate,

View File

@ -304,7 +304,8 @@ MaybeHandle<JSObject> InstantiateObject(Isolate* isolate,
JSFunction::cast(*new_target)->shared()->function_data() ==
info->constructor() &&
JSFunction::cast(*new_target)->context()->native_context() ==
isolate->context()->native_context()) {
isolate->context()->native_context() &&
!info->immutable_proto()) {
constructor = Handle<JSFunction>::cast(new_target);
} else {
// Disable caching for subclass instantiation.
@ -346,6 +347,9 @@ MaybeHandle<JSObject> InstantiateObject(Isolate* isolate,
ASSIGN_RETURN_ON_EXCEPTION(
isolate, result,
ConfigureInstance(isolate, object, info, is_hidden_prototype), JSObject);
if (info->immutable_proto()) {
JSObject::SetImmutableProto(object);
}
// TODO(dcarney): is this necessary?
JSObject::MigrateSlowToFast(result, 0, "ApiNatives::InstantiateObject");
@ -572,8 +576,7 @@ Handle<JSFunction> ApiNatives::CreateApiFunction(
if (!obj->instance_template()->IsUndefined(isolate)) {
Handle<ObjectTemplateInfo> instance_template = Handle<ObjectTemplateInfo>(
ObjectTemplateInfo::cast(obj->instance_template()));
internal_field_count =
Smi::cast(instance_template->internal_field_count())->value();
internal_field_count = instance_template->internal_field_count();
}
// TODO(svenpanne) Kill ApiInstanceType and refactor things by generalizing

View File

@ -1417,7 +1417,7 @@ static Local<ObjectTemplate> ObjectTemplateNew(
obj->set_serial_number(i::Smi::FromInt(next_serial_number));
if (!constructor.IsEmpty())
obj->set_constructor(*Utils::OpenHandle(*constructor));
obj->set_internal_field_count(i::Smi::FromInt(0));
obj->set_data(i::Smi::FromInt(0));
return Utils::ToLocal(obj);
}
@ -1712,7 +1712,7 @@ void ObjectTemplate::SetCallAsFunctionHandler(FunctionCallback callback,
int ObjectTemplate::InternalFieldCount() {
return i::Smi::cast(Utils::OpenHandle(this)->internal_field_count())->value();
return Utils::OpenHandle(this)->internal_field_count();
}
@ -1730,9 +1730,18 @@ void ObjectTemplate::SetInternalFieldCount(int value) {
// function to do the setting.
EnsureConstructor(isolate, this);
}
Utils::OpenHandle(this)->set_internal_field_count(i::Smi::FromInt(value));
Utils::OpenHandle(this)->set_internal_field_count(value);
}
bool ObjectTemplate::IsImmutableProto() {
return Utils::OpenHandle(this)->immutable_proto();
}
void ObjectTemplate::SetImmutableProto() {
i::Isolate* isolate = Utils::OpenHandle(this)->GetIsolate();
ENTER_V8(isolate);
Utils::OpenHandle(this)->set_immutable_proto(true);
}
// --- S c r i p t s ---

View File

@ -599,6 +599,8 @@ Handle<JSFunction> Genesis::CreateEmptyFunction(Isolate* isolate) {
Handle<Map> map = Map::Copy(handle(object_function_prototype->map()),
"EmptyObjectPrototype");
map->set_is_prototype_map(true);
// Ban re-setting Object.prototype.__proto__ to prevent Proxy security bug
map->set_immutable_proto(true);
object_function_prototype->set_map(*map);
native_context()->set_initial_object_prototype(*object_function_prototype);

View File

@ -128,6 +128,8 @@ class CallSite {
T(FunctionBind, "Bind must be called on a function") \
T(GeneratorRunning, "Generator is already running") \
T(IllegalInvocation, "Illegal invocation") \
T(ImmutablePrototypeSet, \
"Immutable prototype object '%' cannot have their prototype set") \
T(IncompatibleMethodReceiver, "Method % called on incompatible receiver %") \
T(InstanceofNonobjectProto, \
"Function has non-object prototype '%' in instanceof check") \

View File

@ -991,7 +991,7 @@ void ObjectTemplateInfo::ObjectTemplateInfoVerify() {
CHECK(IsObjectTemplateInfo());
TemplateInfoVerify();
VerifyPointer(constructor());
VerifyPointer(internal_field_count());
VerifyPointer(data());
}

View File

@ -4725,6 +4725,13 @@ bool Map::is_migration_target() {
return IsMigrationTarget::decode(bit_field3());
}
void Map::set_immutable_proto(bool value) {
set_bit_field3(ImmutablePrototype::update(bit_field3(), value));
}
bool Map::is_immutable_proto() {
return ImmutablePrototype::decode(bit_field3());
}
void Map::set_new_target_is_base(bool value) {
set_bit_field3(NewTargetIsBase::update(bit_field3(), value));
@ -5639,8 +5646,25 @@ ACCESSORS(FunctionTemplateInfo, shared_function_info, Object,
SMI_ACCESSORS(FunctionTemplateInfo, flag, kFlagOffset)
ACCESSORS(ObjectTemplateInfo, constructor, Object, kConstructorOffset)
ACCESSORS(ObjectTemplateInfo, internal_field_count, Object,
kInternalFieldCountOffset)
ACCESSORS(ObjectTemplateInfo, data, Object, kDataOffset)
int ObjectTemplateInfo::internal_field_count() const {
Object* value = data();
DCHECK(value->IsSmi());
return InternalFieldCount::decode(Smi::cast(value)->value());
}
void ObjectTemplateInfo::set_internal_field_count(int count) {
return set_data(Smi::FromInt(
InternalFieldCount::update(Smi::cast(data())->value(), count)));
}
bool ObjectTemplateInfo::immutable_proto() const {
Object* value = data();
DCHECK(value->IsSmi());
return IsImmutablePrototype::decode(Smi::cast(value)->value());
}
void ObjectTemplateInfo::set_immutable_proto(bool immutable) {
return set_data(Smi::FromInt(
IsImmutablePrototype::update(Smi::cast(data())->value(), immutable)));
}
ACCESSORS(AllocationSite, transition_info, Object, kTransitionInfoOffset)
ACCESSORS(AllocationSite, nested_site, Object, kNestedSiteOffset)

View File

@ -1133,7 +1133,8 @@ void ObjectTemplateInfo::ObjectTemplateInfoPrint(std::ostream& os) { // NOLINT
os << "\n - property_list: " << Brief(property_list());
os << "\n - property_accessors: " << Brief(property_accessors());
os << "\n - constructor: " << Brief(constructor());
os << "\n - internal_field_count: " << Brief(internal_field_count());
os << "\n - internal_field_count: " << internal_field_count();
os << "\n - immutable_proto: " << (immutable_proto() ? "true" : "false");
os << "\n";
}

View File

@ -8682,6 +8682,17 @@ Handle<Map> Map::CopyNormalized(Handle<Map> map,
return result;
}
// Return an immutable prototype exotic object version of the input map.
// Never even try to cache it in the transition tree, as it is intended
// for the global object and its prototype chain, and excluding it saves
// memory on the map transition tree.
// static
Handle<Map> Map::TransitionToImmutableProto(Handle<Map> map) {
Handle<Map> new_map = Map::Copy(map, "ImmutablePrototype");
new_map->set_immutable_proto(true);
return new_map;
}
Handle<Map> Map::CopyInitialMap(Handle<Map> map, int instance_size,
int in_object_properties,
@ -14920,6 +14931,13 @@ Maybe<bool> JSObject::SetPrototype(Handle<JSObject> object,
// Nothing to do if prototype is already set.
if (map->prototype() == *value) return Just(true);
bool immutable_proto = object->map()->is_immutable_proto();
if (immutable_proto) {
RETURN_FAILURE(
isolate, should_throw,
NewTypeError(MessageTemplate::kImmutablePrototypeSet, object));
}
// From 8.6.2 Object Internal Methods
// ...
// In addition, if [[Extensible]] is false the value of the [[Class]] and
@ -14971,6 +14989,17 @@ Maybe<bool> JSObject::SetPrototype(Handle<JSObject> object,
return Just(true);
}
// static
void JSObject::SetImmutableProto(Handle<JSObject> object) {
DCHECK(!object->IsAccessCheckNeeded()); // Never called from JS
Handle<Map> map(object->map());
// Nothing to do if prototype is already set.
if (map->is_immutable_proto()) return;
Handle<Map> new_map = Map::TransitionToImmutableProto(map);
object->set_map(*new_map);
}
void JSObject::EnsureCanContainElements(Handle<JSObject> object,
Arguments* args,

View File

@ -2346,6 +2346,10 @@ class JSObject: public JSReceiver {
bool from_javascript,
ShouldThrow should_throw);
// Makes the object prototype immutable
// Never called from JavaScript
static void SetImmutableProto(Handle<JSObject> object);
// Initializes the body starting at |start_offset|. It is responsibility of
// the caller to initialize object header. Fill the pre-allocated fields with
// pre_allocated_value and the rest with filler_value.
@ -5702,7 +5706,7 @@ class Map: public HeapObject {
class Deprecated : public BitField<bool, 23, 1> {};
class IsUnstable : public BitField<bool, 24, 1> {};
class IsMigrationTarget : public BitField<bool, 25, 1> {};
// Bit 26 is free.
class ImmutablePrototype : public BitField<bool, 26, 1> {};
class NewTargetIsBase : public BitField<bool, 27, 1> {};
// Bit 28 is free.
@ -5990,6 +5994,8 @@ class Map: public HeapObject {
inline bool is_stable();
inline void set_migration_target(bool value);
inline bool is_migration_target();
inline void set_immutable_proto(bool value);
inline bool is_immutable_proto();
inline void set_construction_counter(int value);
inline int construction_counter();
inline void deprecate();
@ -6162,6 +6168,8 @@ class Map: public HeapObject {
Handle<Object> prototype,
PrototypeOptimizationMode mode);
static Handle<Map> TransitionToImmutableProto(Handle<Map> map);
static const int kMaxPreAllocatedPropertyFields = 255;
// Layout description.
@ -10716,7 +10724,9 @@ class FunctionTemplateInfo: public TemplateInfo {
class ObjectTemplateInfo: public TemplateInfo {
public:
DECL_ACCESSORS(constructor, Object)
DECL_ACCESSORS(internal_field_count, Object)
DECL_ACCESSORS(data, Object)
DECL_INT_ACCESSORS(internal_field_count)
DECL_BOOLEAN_ACCESSORS(immutable_proto)
DECLARE_CAST(ObjectTemplateInfo)
@ -10725,9 +10735,14 @@ class ObjectTemplateInfo: public TemplateInfo {
DECLARE_VERIFIER(ObjectTemplateInfo)
static const int kConstructorOffset = TemplateInfo::kHeaderSize;
static const int kInternalFieldCountOffset =
kConstructorOffset + kPointerSize;
static const int kSize = kInternalFieldCountOffset + kPointerSize;
// LSB is for immutable_proto, higher bits for internal_field_count
static const int kDataOffset = kConstructorOffset + kPointerSize;
static const int kSize = kDataOffset + kPointerSize;
private:
class IsImmutablePrototype : public BitField<bool, 0, 1> {};
class InternalFieldCount
: public BitField<int, IsImmutablePrototype::kNext, 29> {};
};

View File

@ -91,7 +91,7 @@ bytecodes: [
B(TestEqualStrict), R(12),
B(JumpIfFalse), U8(4),
B(Jump), U8(18),
B(Wide), B(LdaSmi), U16(128),
B(Wide), B(LdaSmi), U16(129),
B(Star), R(12),
B(LdaConstant), U8(8),
B(Star), R(13),
@ -235,7 +235,7 @@ bytecodes: [
B(TestEqualStrict), R(13),
B(JumpIfFalse), U8(4),
B(Jump), U8(18),
B(Wide), B(LdaSmi), U16(128),
B(Wide), B(LdaSmi), U16(129),
B(Star), R(13),
B(LdaConstant), U8(8),
B(Star), R(14),
@ -392,7 +392,7 @@ bytecodes: [
B(TestEqualStrict), R(12),
B(JumpIfFalse), U8(4),
B(Jump), U8(18),
B(Wide), B(LdaSmi), U16(128),
B(Wide), B(LdaSmi), U16(129),
B(Star), R(12),
B(LdaConstant), U8(8),
B(Star), R(13),
@ -539,7 +539,7 @@ bytecodes: [
B(TestEqualStrict), R(11),
B(JumpIfFalse), U8(4),
B(Jump), U8(18),
B(Wide), B(LdaSmi), U16(128),
B(Wide), B(LdaSmi), U16(129),
B(Star), R(11),
B(LdaConstant), U8(10),
B(Star), R(12),

View File

@ -480,7 +480,7 @@ bytecodes: [
B(TestEqualStrict), R(10),
B(JumpIfFalse), U8(4),
B(Jump), U8(18),
B(Wide), B(LdaSmi), U16(128),
B(Wide), B(LdaSmi), U16(129),
B(Star), R(10),
B(LdaConstant), U8(14),
B(Star), R(11),

View File

@ -25348,3 +25348,32 @@ TEST(PrivateForApiIsNumber) {
// Shouldn't crash.
v8::Private::ForApi(isolate, v8_str("42"));
}
THREADED_TEST(ImmutableProto) {
LocalContext context;
v8::Isolate* isolate = context->GetIsolate();
v8::HandleScope handle_scope(isolate);
Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(isolate);
templ->InstanceTemplate()->SetImmutableProto();
Local<v8::Object> object = templ->GetFunction(context.local())
.ToLocalChecked()
->NewInstance(context.local())
.ToLocalChecked();
// Look up the prototype
Local<v8::Value> original_proto =
object->Get(context.local(), v8_str("__proto__")).ToLocalChecked();
// Setting the prototype (e.g., to null) throws
CHECK(object->SetPrototype(context.local(), v8::Null(isolate)).IsNothing());
// The original prototype is still there
Local<Value> new_proto =
object->Get(context.local(), v8_str("__proto__")).ToLocalChecked();
CHECK(new_proto->IsObject());
CHECK(new_proto.As<v8::Object>()
->Equals(context.local(), original_proto)
.FromJust());
}

View File

@ -5,8 +5,5 @@
var failing_proxy = new Proxy({}, new Proxy({}, {
get() { throw "No trap should fire" }}));
Object.setPrototypeOf(Object.prototype, failing_proxy);
assertThrows(()=>a, TypeError);
Object.setPrototypeOf(this, failing_proxy);
assertThrows(()=>a, TypeError);
assertThrows(() => Object.setPrototypeOf(Object.prototype, failing_proxy), TypeError);
assertThrows(()=>a, ReferenceError);

View File

@ -28,7 +28,7 @@
// See: http://code.google.com/p/v8/issues/detail?id=1403
a = [];
Object.prototype.__proto__ = { __proto__: null };
assertThrows(() => Object.prototype.__proto__ = { __proto__: null }, TypeError);
a.shift();
a = [];

View File

@ -11,7 +11,10 @@ function f(a) {
var rec = new Receiver();
var proto = rec.__proto__.__proto__;
// Formerly, this mutated rec.__proto__.__proto__, but
// the global object prototype chain is now immutable;
// not sure if this test now hits the original hazard case.
var proto = rec.__proto__;
// Initialize prototype chain dependent IC (nonexistent load).
assertEquals(undefined, f(rec));