[wasm-gc] Check for subtyping when importing function

When importing a WasmExportedFunction into a module, we checked that
its type is equivalent with the declared type of the import. Instead,
we should check that the imported function has an isorecursive subtype
of the declared type.

Change-Id: I2a5f68d4c4c8c65a0eed5b82e8e825affb832cfe
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4061732
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#84593}
This commit is contained in:
Manos Koukoutos 2022-12-01 09:31:27 +01:00 committed by V8 LUCI CQ
parent 04e6519dd0
commit 86beeb9910
12 changed files with 90 additions and 45 deletions

View File

@ -7906,12 +7906,12 @@ bool ResolveBoundJSFastApiFunction(const wasm::FunctionSig* expected_sig,
WasmImportData ResolveWasmImportCall(
Handle<JSReceiver> callable, const wasm::FunctionSig* expected_sig,
const wasm::WasmModule* module,
uint32_t expected_canonical_type_index, const wasm::WasmModule* module,
const wasm::WasmFeatures& enabled_features) {
Isolate* isolate = callable->GetIsolate();
if (WasmExportedFunction::IsWasmExportedFunction(*callable)) {
auto imported_function = Handle<WasmExportedFunction>::cast(callable);
if (!imported_function->MatchesSignature(module, expected_sig)) {
if (!imported_function->MatchesSignature(expected_canonical_type_index)) {
return {WasmImportCallKind::kLinkError, callable, wasm::kNoSuspend};
}
uint32_t func_index =

View File

@ -120,7 +120,8 @@ struct WasmImportData {
// is why the ultimate target is returned as well.
V8_EXPORT_PRIVATE WasmImportData ResolveWasmImportCall(
Handle<JSReceiver> callable, const wasm::FunctionSig* sig,
const wasm::WasmModule* module, const wasm::WasmFeatures& enabled_features);
uint32_t expected_canonical_type_index, const wasm::WasmModule* module,
const wasm::WasmFeatures& enabled_features);
// Compiles an import call wrapper, which allows Wasm to call imports.
V8_EXPORT_PRIVATE wasm::WasmCompilationResult CompileWasmImportCallWrapper(

View File

@ -1676,8 +1676,8 @@ Handle<WasmResumeData> Factory::NewWasmResumeData(
Handle<WasmExportedFunctionData> Factory::NewWasmExportedFunctionData(
Handle<CodeT> export_wrapper, Handle<WasmInstanceObject> instance,
Address call_target, Handle<Object> ref, int func_index,
const wasm::FunctionSig* sig, int wrapper_budget, Handle<Map> rtt,
wasm::Promise promise) {
const wasm::FunctionSig* sig, uint32_t canonical_type_index,
int wrapper_budget, Handle<Map> rtt, wasm::Promise promise) {
Handle<WasmInternalFunction> internal =
NewWasmInternalFunction(call_target, Handle<HeapObject>::cast(ref), rtt);
Map map = *wasm_exported_function_data_map();
@ -1691,6 +1691,7 @@ Handle<WasmExportedFunctionData> Factory::NewWasmExportedFunctionData(
result.set_instance(*instance);
result.set_function_index(func_index);
result.init_sig(isolate(), sig);
result.set_canonical_type_index(canonical_type_index);
result.set_wrapper_budget(wrapper_budget);
// We can't skip the write barrier when V8_EXTERNAL_CODE_SPACE is enabled
// because in this case the CodeT (CodeDataContainer) objects are not

View File

@ -636,8 +636,8 @@ class V8_EXPORT_PRIVATE Factory : public FactoryBase<Factory> {
Handle<WasmExportedFunctionData> NewWasmExportedFunctionData(
Handle<CodeT> export_wrapper, Handle<WasmInstanceObject> instance,
Address call_target, Handle<Object> ref, int func_index,
const wasm::FunctionSig* sig, int wrapper_budget, Handle<Map> rtt,
wasm::Promise promise);
const wasm::FunctionSig* sig, uint32_t canonical_type_index,
int wrapper_budget, Handle<Map> rtt, wasm::Promise promise);
Handle<WasmApiFunctionRef> NewWasmApiFunctionRef(
Handle<JSReceiver> callable, wasm::Suspend suspend,
Handle<WasmInstanceObject> instance);

View File

@ -100,23 +100,28 @@ ValueType TypeCanonicalizer::CanonicalizeValueType(
module->isorecursive_canonical_type_ids[type.ref_index()]);
}
bool TypeCanonicalizer::IsCanonicalSubtype(uint32_t sub_index,
uint32_t super_index,
const WasmModule* sub_module,
const WasmModule* super_module) {
bool TypeCanonicalizer::IsCanonicalSubtype(uint32_t canonical_sub_index,
uint32_t canonical_super_index) {
// Multiple threads could try to register and access recursive groups
// concurrently.
// TODO(manoskouk): Investigate if we can improve this synchronization.
base::MutexGuard mutex_guard(&mutex_);
while (canonical_sub_index != kNoSuperType) {
if (canonical_sub_index == canonical_super_index) return true;
canonical_sub_index = canonical_supertypes_[canonical_sub_index];
}
return false;
}
bool TypeCanonicalizer::IsCanonicalSubtype(uint32_t sub_index,
uint32_t super_index,
const WasmModule* sub_module,
const WasmModule* super_module) {
uint32_t canonical_super =
super_module->isorecursive_canonical_type_ids[super_index];
uint32_t canonical_sub =
sub_module->isorecursive_canonical_type_ids[sub_index];
while (canonical_sub != kNoSuperType) {
if (canonical_sub == canonical_super) return true;
canonical_sub = canonical_supertypes_[canonical_sub];
}
return false;
return IsCanonicalSubtype(canonical_sub, canonical_super);
}
TypeCanonicalizer::CanonicalType TypeCanonicalizer::CanonicalizeTypeDef(

View File

@ -52,6 +52,11 @@ class TypeCanonicalizer {
// signature.
V8_EXPORT_PRIVATE uint32_t AddRecursiveGroup(const FunctionSig* sig);
// Returns if {canonical_sub_index} is a canonical subtype of
// {canonical_super_index}.
V8_EXPORT_PRIVATE bool IsCanonicalSubtype(uint32_t canonical_sub_index,
uint32_t canonical_super_index);
// Returns if the type at {sub_index} in {sub_module} is a subtype of the
// type at {super_index} in {super_module} after canonicalization.
V8_EXPORT_PRIVATE bool IsCanonicalSubtype(uint32_t sub_index,

View File

@ -1125,8 +1125,11 @@ bool InstanceBuilder::ProcessImportedFunction(
}
auto js_receiver = Handle<JSReceiver>::cast(value);
const FunctionSig* expected_sig = module_->functions[func_index].sig;
auto resolved = compiler::ResolveWasmImportCall(js_receiver, expected_sig,
module_, enabled_);
uint32_t sig_index = module_->functions[func_index].sig_index;
uint32_t canonical_type_index =
module_->isorecursive_canonical_type_ids[sig_index];
auto resolved = compiler::ResolveWasmImportCall(
js_receiver, expected_sig, canonical_type_index, module_, enabled_);
compiler::WasmImportCallKind kind = resolved.kind;
js_receiver = resolved.callable;
switch (kind) {
@ -1589,8 +1592,11 @@ void InstanceBuilder::CompileImportWrappers(
auto js_receiver = Handle<JSReceiver>::cast(value);
uint32_t func_index = module_->import_table[index].index;
const FunctionSig* sig = module_->functions[func_index].sig;
auto resolved =
compiler::ResolveWasmImportCall(js_receiver, sig, module_, enabled_);
uint32_t sig_index = module_->functions[func_index].sig_index;
uint32_t canonical_type_index =
module_->isorecursive_canonical_type_ids[sig_index];
auto resolved = compiler::ResolveWasmImportCall(
js_receiver, sig, canonical_type_index, module_, enabled_);
compiler::WasmImportCallKind kind = resolved.kind;
if (kind == compiler::WasmImportCallKind::kWasmToWasm ||
kind == compiler::WasmImportCallKind::kLinkError ||
@ -1607,9 +1613,6 @@ void InstanceBuilder::CompileImportWrappers(
expected_arity =
shared.internal_formal_parameter_count_without_receiver();
}
uint32_t canonical_type_index =
module_->isorecursive_canonical_type_ids[module_->functions[func_index]
.sig_index];
WasmImportWrapperCache::CacheKey key(kind, canonical_type_index,
expected_arity, resolved.suspend);
if (cache_scope[key] != nullptr) {

View File

@ -1425,7 +1425,7 @@ void WasmInstanceObject::ImportWasmJSFunctionIntoTable(
// and permissions switching.
const wasm::WasmFeatures enabled = native_module->enabled_features();
auto resolved = compiler::ResolveWasmImportCall(
callable, sig, instance->module(), enabled);
callable, sig, canonical_sig_index, instance->module(), enabled);
compiler::WasmImportCallKind kind = resolved.kind;
callable = resolved.callable; // Update to ultimate target.
DCHECK_NE(compiler::WasmImportCallKind::kLinkError, kind);
@ -1937,10 +1937,13 @@ Handle<WasmExportedFunction> WasmExportedFunction::New(
export_wrapper->builtin_id() == Builtin::kWasmReturnPromiseOnSuspend
? wasm::kPromise
: wasm::kNoPromise;
uint32_t sig_index = instance->module()->functions[func_index].sig_index;
uint32_t canonical_type_index =
instance->module()->isorecursive_canonical_type_ids[sig_index];
Handle<WasmExportedFunctionData> function_data =
factory->NewWasmExportedFunctionData(
export_wrapper, instance, call_target, ref, func_index, sig,
wasm::kGenericWrapperBudget, rtt, promise);
canonical_type_index, wasm::kGenericWrapperBudget, rtt, promise);
MaybeHandle<String> maybe_name;
bool is_asm_js_module = instance->module_object().is_asm_js();
@ -2000,20 +2003,10 @@ const wasm::FunctionSig* WasmExportedFunction::sig() {
}
bool WasmExportedFunction::MatchesSignature(
const WasmModule* other_module, const wasm::FunctionSig* other_sig) {
const wasm::FunctionSig* sig = this->sig();
if (sig->parameter_count() != other_sig->parameter_count() ||
sig->return_count() != other_sig->return_count()) {
return false;
}
for (int i = 0; i < sig->all().size(); i++) {
if (!wasm::EquivalentTypes(sig->all()[i], other_sig->all()[i],
this->instance().module(), other_module)) {
return false;
}
}
return true;
uint32_t other_canonical_type_index) {
return wasm::GetWasmEngine()->type_canonicalizer()->IsCanonicalSubtype(
this->shared().wasm_exported_function_data().canonical_type_index(),
other_canonical_type_index);
}
// static

View File

@ -618,8 +618,7 @@ class WasmExportedFunction : public JSFunction {
V8_EXPORT_PRIVATE const wasm::FunctionSig* sig();
bool MatchesSignature(const wasm::WasmModule* other_module,
const wasm::FunctionSig* other_sig);
bool MatchesSignature(uint32_t other_canonical_sig_index);
// Return a null-terminated string with the debug name in the form
// 'js-to-wasm:<sig>'.

View File

@ -71,6 +71,7 @@ extern class WasmExportedFunctionData extends WasmFunctionData {
@if(V8_EXTERNAL_CODE_SPACE) c_wrapper_code: CodeDataContainer;
@ifnot(V8_EXTERNAL_CODE_SPACE) c_wrapper_code: Code;
packed_args_size: Smi;
canonical_type_index: Smi;
sig: ExternalPointer; // wasm::FunctionSig*
}

View File

@ -75,15 +75,15 @@ TestingModuleBuilder::TestingModuleBuilder(
if (maybe_import) {
// Manually compile an import wrapper and insert it into the instance.
uint32_t canonical_type_index =
GetTypeCanonicalizer()->AddRecursiveGroup(maybe_import->sig);
auto resolved = compiler::ResolveWasmImportCall(
maybe_import->js_function, maybe_import->sig,
maybe_import->js_function, maybe_import->sig, canonical_type_index,
instance_object_->module(), enabled_features_);
compiler::WasmImportCallKind kind = resolved.kind;
Handle<JSReceiver> callable = resolved.callable;
WasmImportWrapperCache::ModificationScope cache_scope(
native_module_->import_wrapper_cache());
uint32_t canonical_type_index =
GetTypeCanonicalizer()->AddRecursiveGroup(maybe_import->sig);
WasmImportWrapperCache::CacheKey key(
kind, canonical_type_index,
static_cast<int>(maybe_import->sig->parameter_count()), kNoSuspend);

View File

@ -6,7 +6,8 @@
d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
(function Test1() {
(function TestImportedRefCall() {
print(arguments.callee.name);
var exporting_instance = (function () {
var builder = new WasmModuleBuilder();
@ -120,6 +121,7 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
})();
(function TestFromJSSlowPath() {
print(arguments.callee.name);
var builder = new WasmModuleBuilder();
var sig_index = builder.addType(kSig_i_i);
@ -135,3 +137,38 @@ d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");
// {undefined} is converted to 0.
assertEquals(0, instance.exports.main(fun, 1000));
})();
(function TestImportedFunctionSubtyping() {
print(arguments.callee.name);
var exporting_instance = (function () {
var builder = new WasmModuleBuilder();
let super_struct = builder.addStruct([makeField(kWasmI32, true)]);
let sub_struct = builder.addStruct(
[makeField(kWasmI32, true), makeField(kWasmI64, true)], super_struct);
let super_sig = builder.addType(makeSig([wasmRefNullType(sub_struct)],
[kWasmI32]))
let sub_sig = builder.addType(makeSig([wasmRefNullType(super_struct)],
[kWasmI32]), super_sig)
builder.addFunction("exported_function", sub_sig)
.addBody([kExprLocalGet, 0, kGCPrefix, kExprStructGet, super_struct, 0])
.exportFunc();
return builder.instantiate({});
})();
var instance = (function () {
var builder = new WasmModuleBuilder();
// These should canonicalize to the same types as the exporting instance.
let super_struct = builder.addStruct([makeField(kWasmI32, true)]);
let sub_struct = builder.addStruct(
[makeField(kWasmI32, true), makeField(kWasmI64, true)], super_struct);
let super_sig = builder.addType(makeSig([wasmRefNullType(sub_struct)],
[kWasmI32]))
builder.addImport("m", "f", super_sig);
// Import is a function of the declared type.
return builder.instantiate({m: {f:
exporting_instance.exports.exported_function}});
})();
})();