[api] Implement v8::Context::HasTemplateLiteralObject

V8-side implementation for trusted types fromLiteral.

Create a separate JSArray root map for template literal objects to
clearly identify untampered template literals belonging to a given
context.

Given that template literals are frozen arrays with a 'raw' property,
we don't expect additional polymorphism.

Drive-by-fix:
Avoid ValidateElements call in NewJSArrayWithElements.

Bug: chromium:1271149
Change-Id: I327b0fd99a2db3b57d35efa9293ddf2f14e555ea
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3572044
Reviewed-by: Shu-yu Guo <syg@chromium.org>
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Auto-Submit: Camillo Bruni <cbruni@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83383}
This commit is contained in:
Camillo 2022-09-22 10:39:10 +02:00 committed by V8 LUCI CQ
parent e2b7092f52
commit 4e23f53cdf
13 changed files with 251 additions and 30 deletions

View File

@ -290,6 +290,7 @@ class V8_EXPORT Context : public Data {
Local<Function> after_hook,
Local<Function> resolve_hook);
bool HasTemplateLiteralObject(Local<Value> object);
/**
* Stack-allocated class which sets the execution context for all
* operations executed within a local scope.

View File

@ -6715,6 +6715,14 @@ void v8::Context::SetPromiseHooks(Local<Function> init_hook,
#endif // V8_ENABLE_JAVASCRIPT_PROMISE_HOOKS
}
bool Context::HasTemplateLiteralObject(Local<Value> object) {
i::DisallowGarbageCollection no_gc;
i::Object i_object = *Utils::OpenHandle(*object);
if (!i_object.IsJSArray()) return false;
return Utils::OpenHandle(this)->native_context().HasTemplateLiteralObject(
i::JSArray::cast(i_object));
}
MaybeLocal<Context> metrics::Recorder::GetContext(
Isolate* v8_isolate, metrics::Recorder::ContextId id) {
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(v8_isolate);

View File

@ -2730,6 +2730,10 @@ Handle<JSObject> Factory::NewJSObjectFromMap(
InitializeJSObjectFromMap(js_obj, *empty_fixed_array(), *map);
DCHECK(js_obj.HasFastElements() ||
(isolate()->bootstrapper()->IsActive() ||
*map == isolate()
->raw_native_context()
.js_array_template_literal_object_map()) ||
js_obj.HasTypedArrayOrRabGsabTypedArrayElements() ||
js_obj.HasFastStringWrapperElements() ||
js_obj.HasFastArgumentsElements() || js_obj.HasDictionaryElements() ||
@ -2802,7 +2806,9 @@ Handle<JSArray> Factory::NewJSArrayWithElements(Handle<FixedArrayBase> elements,
AllocationType allocation) {
Handle<JSArray> array = NewJSArrayWithUnverifiedElements(
elements, elements_kind, length, allocation);
#ifdef ENABLE_SLOW_DCHECKS
JSObject::ValidateElements(*array);
#endif
return array;
}
@ -2816,8 +2822,14 @@ Handle<JSArray> Factory::NewJSArrayWithUnverifiedElements(
JSFunction array_function = native_context.array_function();
map = array_function.initial_map();
}
Handle<JSArray> array = Handle<JSArray>::cast(
NewJSObjectFromMap(handle(map, isolate()), allocation));
return NewJSArrayWithUnverifiedElements(handle(map, isolate()), elements,
length, allocation);
}
Handle<JSArray> Factory::NewJSArrayWithUnverifiedElements(
Handle<Map> map, Handle<FixedArrayBase> elements, int length,
AllocationType allocation) {
auto array = Handle<JSArray>::cast(NewJSObjectFromMap(map, allocation));
DisallowGarbageCollection no_gc;
JSArray raw = *array;
raw.set_elements(*elements);
@ -2825,6 +2837,23 @@ Handle<JSArray> Factory::NewJSArrayWithUnverifiedElements(
return array;
}
Handle<JSArray> Factory::NewJSArrayForTemplateLiteralArray(
Handle<FixedArray> cooked_strings, Handle<FixedArray> raw_strings) {
Handle<JSArray> raw_object =
NewJSArrayWithElements(raw_strings, PACKED_ELEMENTS,
raw_strings->length(), AllocationType::kOld);
JSObject::SetIntegrityLevel(raw_object, FROZEN, kThrowOnError).ToChecked();
Handle<NativeContext> native_context = isolate()->native_context();
Handle<JSArray> template_object = NewJSArrayWithUnverifiedElements(
handle(native_context->js_array_template_literal_object_map(), isolate()),
cooked_strings, cooked_strings->length(), AllocationType::kOld);
TemplateLiteralObject::SetRaw(template_object, raw_object);
DCHECK_EQ(template_object->map(),
native_context->js_array_template_literal_object_map());
return template_object;
}
void Factory::NewJSArrayStorage(Handle<JSArray> array, int length, int capacity,
ArrayStorageAllocationMode mode) {
DCHECK(capacity >= length);

View File

@ -606,6 +606,9 @@ class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
ElementsKind elements_kind = TERMINAL_FAST_ELEMENTS_KIND,
AllocationType allocation = AllocationType::kYoung);
Handle<JSArray> NewJSArrayForTemplateLiteralArray(
Handle<FixedArray> cooked_strings, Handle<FixedArray> raw_strings);
void NewJSArrayStorage(
Handle<JSArray> array, int length, int capacity,
ArrayStorageAllocationMode mode = DONT_INITIALIZE_ARRAY_ELEMENTS);
@ -1141,6 +1144,9 @@ class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
Handle<JSArray> NewJSArrayWithUnverifiedElements(
Handle<FixedArrayBase> elements, ElementsKind elements_kind, int length,
AllocationType allocation = AllocationType::kYoung);
Handle<JSArray> NewJSArrayWithUnverifiedElements(
Handle<Map> map, Handle<FixedArrayBase> elements, int length,
AllocationType allocation = AllocationType::kYoung);
// Creates the backing storage for a JSArray. This handle must be discarded
// before returning the JSArray reference to code outside Factory, which might

View File

@ -68,6 +68,7 @@
#include "src/objects/js-weak-refs.h"
#include "src/objects/ordered-hash-table.h"
#include "src/objects/property-cell.h"
#include "src/objects/property-descriptor.h"
#include "src/objects/slots-inl.h"
#include "src/objects/swiss-name-dictionary-inl.h"
#include "src/objects/templates.h"
@ -1774,9 +1775,7 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
native_context()->set_initial_array_prototype(*proto);
InitializeJSArrayMaps(isolate_, native_context(),
handle(array_function->initial_map(), isolate_));
SimpleInstallFunction(isolate_, array_function, "isArray",
Builtin::kArrayIsArray, 1, true);
SimpleInstallFunction(isolate_, array_function, "from", Builtin::kArrayFrom,
@ -5794,6 +5793,58 @@ bool Genesis::InstallABunchOfRandomThings() {
native_context()->set_data_property_descriptor_map(*map);
}
{
// -- TemplateLiteral JSArray Map
Handle<JSFunction> array_function(native_context()->array_function(),
isolate());
Handle<Map> template_map(array_function->initial_map(), isolate_);
template_map = Map::CopyAsElementsKind(isolate_, template_map,
PACKED_ELEMENTS, OMIT_TRANSITION);
template_map->set_instance_size(template_map->instance_size() +
kTaggedSize);
// Temporarily instantiate full template_literal_object to get the final
// map.
auto template_object =
Handle<JSArray>::cast(factory()->NewJSObjectFromMap(template_map));
{
DisallowGarbageCollection no_gc;
JSArray raw = *template_object;
raw.set_elements(ReadOnlyRoots(isolate()).empty_fixed_array());
raw.set_length(Smi::FromInt(0));
}
// Install a "raw" data property for {raw_object} on {template_object}.
// See ES#sec-gettemplateobject.
PropertyDescriptor raw_desc;
// Use arbrirary object {template_object} as ".raw" value.
raw_desc.set_value(template_object);
raw_desc.set_configurable(false);
raw_desc.set_enumerable(false);
raw_desc.set_writable(false);
JSArray::DefineOwnProperty(isolate(), template_object,
factory()->raw_string(), &raw_desc,
Just(kThrowOnError))
.ToChecked();
// Freeze the {template_object} as well.
JSObject::SetIntegrityLevel(template_object, FROZEN, kThrowOnError)
.ToChecked();
{
DisallowGarbageCollection no_gc;
// Verify TemplateLiteralObject::kRawFieldOffset
DescriptorArray desc = template_object->map().instance_descriptors();
InternalIndex descriptor_index =
desc.Search(*factory()->raw_string(), desc.number_of_descriptors());
FieldIndex index =
FieldIndex::ForDescriptor(template_object->map(), descriptor_index);
CHECK(index.is_inobject());
CHECK_EQ(index.offset(), TemplateLiteralObject::kRawFieldOffset);
}
native_context()->set_js_array_template_literal_object_map(
template_object->map());
}
// Create a constructor for RegExp results (a variant of Array that
// predefines the properties index, input, and groups).
{

View File

@ -426,6 +426,10 @@ Handle<Object> Context::Lookup(Handle<Context> context, Handle<String> name,
return Handle<Object>::null();
}
bool NativeContext::HasTemplateLiteralObject(JSArray array) {
return array.map() == js_array_template_literal_object_map();
}
void NativeContext::AddOptimizedCode(CodeT code) {
DCHECK(CodeKindCanDeoptimize(code.kind()));
DCHECK(code.next_code_link().IsUndefined());

View File

@ -178,6 +178,8 @@ enum ContextLookupFlags {
js_array_packed_double_elements_map) \
V(JS_ARRAY_HOLEY_DOUBLE_ELEMENTS_MAP_INDEX, Map, \
js_array_holey_double_elements_map) \
V(JS_ARRAY_TEMPLATE_LITERAL_OBJECT_MAP, Map, \
js_array_template_literal_object_map) \
V(JS_ATOMICS_CONDITION_MAP, Map, js_atomics_condition_map) \
V(JS_ATOMICS_MUTEX_MAP, Map, js_atomics_mutex_map) \
V(JS_MAP_FUN_INDEX, JSFunction, js_map_fun) \
@ -746,6 +748,8 @@ class NativeContext : public Context {
inline Map TypedArrayElementsKindToRabGsabCtorMap(
ElementsKind element_kind) const;
bool HasTemplateLiteralObject(JSArray array);
// Dispatched behavior.
DECL_PRINTER(NativeContext)
DECL_VERIFIER(NativeContext)

View File

@ -73,6 +73,14 @@ void JSArrayIterator::set_kind(IterationKind kind) {
set_raw_kind(static_cast<int>(kind));
}
// static
void TemplateLiteralObject::SetRaw(Handle<JSArray> template_object,
Handle<JSArray> raw_object) {
TaggedField<Object, kRawFieldOffset>::store(*template_object, *raw_object);
CONDITIONAL_WRITE_BARRIER(*template_object, kRawFieldOffset, *raw_object,
UPDATE_WRITE_BARRIER);
}
} // namespace internal
} // namespace v8

View File

@ -159,6 +159,14 @@ class JSArrayIterator
TQ_OBJECT_CONSTRUCTORS(JSArrayIterator)
};
// Helper class for JSArrays that are template literal objects
class TemplateLiteralObject {
public:
static const int kRawFieldOffset = JSArray::kLengthOffset + kTaggedSize;
static inline void SetRaw(Handle<JSArray> template_object,
Handle<JSArray> raw_object);
};
} // namespace internal
} // namespace v8

View File

@ -75,33 +75,10 @@ Handle<JSArray> TemplateObjectDescription::GetTemplateObject(
// Create the raw object from the {raw_strings}.
Handle<FixedArray> raw_strings(description->raw_strings(), isolate);
Handle<JSArray> raw_object = isolate->factory()->NewJSArrayWithElements(
raw_strings, PACKED_ELEMENTS, raw_strings->length(),
AllocationType::kOld);
// Create the template object from the {cooked_strings}.
Handle<FixedArray> cooked_strings(description->cooked_strings(), isolate);
Handle<JSArray> template_object = isolate->factory()->NewJSArrayWithElements(
cooked_strings, PACKED_ELEMENTS, cooked_strings->length(),
AllocationType::kOld);
// Freeze the {raw_object}.
JSObject::SetIntegrityLevel(raw_object, FROZEN, kThrowOnError).ToChecked();
// Install a "raw" data property for {raw_object} on {template_object}.
PropertyDescriptor raw_desc;
raw_desc.set_value(raw_object);
raw_desc.set_configurable(false);
raw_desc.set_enumerable(false);
raw_desc.set_writable(false);
JSArray::DefineOwnProperty(isolate, template_object,
isolate->factory()->raw_string(), &raw_desc,
Just(kThrowOnError))
.ToChecked();
// Freeze the {template_object} as well.
JSObject::SetIntegrityLevel(template_object, FROZEN, kThrowOnError)
.ToChecked();
Handle<JSArray> template_object =
isolate->factory()->NewJSArrayForTemplateLiteralArray(cooked_strings,
raw_strings);
// Insert the template object into the template weakmap.
Handle<CachedTemplateObject> cached_template;

View File

@ -238,6 +238,7 @@ v8_source_set("unittests_sources") {
"api/access-check-unittest.cc",
"api/accessor-unittest.cc",
"api/api-icu-unittest.cc",
"api/context-unittest.cc",
"api/deserialize-unittest.cc",
"api/exception-unittest.cc",
"api/gc-callbacks-unittest.cc",

View File

@ -0,0 +1,96 @@
// Copyright 2022 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 "include/libplatform/libplatform.h"
#include "include/v8-context.h"
#include "include/v8-data.h"
#include "include/v8-isolate.h"
#include "include/v8-local-handle.h"
#include "include/v8-value.h"
#include "test/unittests/test-utils.h"
#include "testing/gtest/include/gtest/gtest.h"
using ContextTest = v8::TestWithIsolate;
TEST_F(ContextTest, HasTemplateLiteralObjectBasic) {
v8::Local<v8::Context> context = v8::Context::New(isolate());
v8::Context::Scope scope(context);
ASSERT_FALSE(
context->HasTemplateLiteralObject(v8::Number::New(isolate(), 1)));
ASSERT_FALSE(context->HasTemplateLiteralObject(v8::String::Empty(isolate())));
ASSERT_FALSE(
context->HasTemplateLiteralObject(v8::Array::New(isolate(), 10)));
}
TEST_F(ContextTest, HasTemplateLiteralObject) {
const char* source = R"(
function ret(literal) {
return literal;
};
ret`one_${'two'}_three`;
)";
const char* otherObject1Source = R"(
Object.freeze(
Object.defineProperty(['one_', '_three'], 'raw', {
value: ['asdf'],
writable: false,
enumerable: false,
configurable: false,
})
);
)";
const char* otherObject2Source = R"(
Object.freeze(
Object.defineProperty(['one_', '_three'], 'raw', {
get() { return ['asdf']; },
enumerable: false,
configurable: false,
})
);
)";
v8::Local<v8::Context> context1 = v8::Context::New(isolate());
v8::Local<v8::Value> templateLiteral1;
v8::Local<v8::Value> templateLiteral1_2;
v8::Local<v8::Value> otherObject1_ctx1;
v8::Local<v8::Value> otherObject2_ctx1;
{
v8::Context::Scope scope(context1);
auto script =
v8::Script::Compile(context1, NewString(source)).ToLocalChecked();
templateLiteral1 = script->Run(context1).ToLocalChecked();
templateLiteral1_2 = script->Run(context1).ToLocalChecked();
otherObject1_ctx1 = RunJS(context1, otherObject1Source);
otherObject2_ctx1 = RunJS(context1, otherObject2Source);
}
v8::Local<v8::Value> templateLiteral2;
v8::Local<v8::Context> context2 = v8::Context::New(isolate());
v8::Local<v8::Value> otherObject1_ctx2;
v8::Local<v8::Value> otherObject2_ctx2;
{
v8::Context::Scope scope(context2);
templateLiteral2 = RunJS(context2, source);
otherObject1_ctx2 = RunJS(context2, otherObject1Source);
otherObject2_ctx2 = RunJS(context1, otherObject2Source);
}
ASSERT_TRUE(context1->HasTemplateLiteralObject(templateLiteral1));
ASSERT_TRUE(context1->HasTemplateLiteralObject(templateLiteral1_2));
ASSERT_FALSE(context1->HasTemplateLiteralObject(templateLiteral2));
ASSERT_FALSE(context2->HasTemplateLiteralObject(templateLiteral1));
ASSERT_FALSE(context2->HasTemplateLiteralObject(templateLiteral1_2));
ASSERT_TRUE(context2->HasTemplateLiteralObject(templateLiteral2));
// Neither otherObject is a template object
ASSERT_FALSE(context1->HasTemplateLiteralObject(otherObject1_ctx1));
ASSERT_FALSE(context1->HasTemplateLiteralObject(otherObject2_ctx1));
ASSERT_FALSE(context1->HasTemplateLiteralObject(otherObject1_ctx2));
ASSERT_FALSE(context1->HasTemplateLiteralObject(otherObject1_ctx1));
ASSERT_FALSE(context2->HasTemplateLiteralObject(otherObject2_ctx1));
ASSERT_FALSE(context2->HasTemplateLiteralObject(otherObject1_ctx2));
ASSERT_FALSE(context2->HasTemplateLiteralObject(otherObject2_ctx2));
ASSERT_FALSE(context2->HasTemplateLiteralObject(otherObject2_ctx2));
}

View File

@ -13,6 +13,7 @@
#include "include/v8-context.h"
#include "include/v8-extension.h"
#include "include/v8-local-handle.h"
#include "include/v8-object.h"
#include "include/v8-primitive.h"
#include "include/v8-template.h"
#include "src/api/api-inl.h"
@ -100,6 +101,18 @@ class WithIsolateMixin : public TMixin {
v8::Isolate* v8_isolate() const { return isolate_wrapper_.isolate(); }
Local<Value> RunJS(const char* source, Local<Context> context) {
return RunJS(
v8::String::NewFromUtf8(this->v8_isolate(), source).ToLocalChecked(),
context);
}
Local<Value> RunJS(Local<String> source, Local<Context> context) {
Local<Script> script =
v8::Script::Compile(context, source).ToLocalChecked();
return script->Run(context).ToLocalChecked();
}
private:
v8::IsolateWrapper isolate_wrapper_;
};
@ -134,6 +147,12 @@ class WithIsolateScopeMixin : public TMixin {
v8::String::NewFromUtf8(this->v8_isolate(), source).ToLocalChecked());
}
Local<Value> RunJS(Local<Context> context, const char* source) {
return RunJS(
context,
v8::String::NewFromUtf8(this->v8_isolate(), source).ToLocalChecked());
}
MaybeLocal<Value> TryRunJS(const char* source) {
return TryRunJS(
v8::String::NewFromUtf8(this->v8_isolate(), source).ToLocalChecked());
@ -141,6 +160,11 @@ class WithIsolateScopeMixin : public TMixin {
static MaybeLocal<Value> TryRunJS(Isolate* isolate, Local<String> source) {
auto context = isolate->GetCurrentContext();
return TryRunJS(context, source);
}
static MaybeLocal<Value> TryRunJS(Local<Context> context,
Local<String> source) {
v8::Local<v8::Value> result;
Local<Script> script =
v8::Script::Compile(context, source).ToLocalChecked();
@ -210,6 +234,10 @@ class WithIsolateScopeMixin : public TMixin {
return TryRunJS(source).ToLocalChecked();
}
Local<Value> RunJS(Local<Context> context, Local<String> source) {
return TryRunJS(context, source).ToLocalChecked();
}
MaybeLocal<Value> TryRunJS(Local<String> source) {
return TryRunJS(this->v8_isolate(), source);
}