dart-sdk/runtime/lib/function.cc
Alexander Markov 8ff777e61f [vm] Fix equality of uninstantiated generic closures
Equality of uninstantiated generic closures should match equality
of corresponding instantiated generic closures.

We cannot use identity for equality of instantiated generic closures
as distinct instantiations of the same generic closure should be equal.
So, identity shouldn't be used for uninstantiated generic closures
neither.

This change fixes equality for uninstantiated generic closures
and also updates closure hashCode correspondingly.

TEST=language/closure/instantiation_closure_equality_test
TEST=co19/LanguageFeatures/Constructor-tear-offs/equality_*

Fixes https://github.com/dart-lang/sdk/issues/51660

Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-nnbd-linux-release-x64-try
Change-Id: Ieafc052de4a4f5f9ffcd2d9d26cb36a209c0e127
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/287581
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Markov <alexmarkov@google.com>
2023-03-08 21:44:56 +00:00

123 lines
4.8 KiB
C++

// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "vm/bootstrap_natives.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/dart_entry.h"
#include "vm/exceptions.h"
#include "vm/native_entry.h"
#include "vm/object.h"
#include "vm/symbols.h"
namespace dart {
DEFINE_NATIVE_ENTRY(Function_apply, 0, 2) {
const int kTypeArgsLen = 0; // TODO(regis): Add support for generic function.
const Array& fun_arguments =
Array::CheckedHandle(zone, arguments->NativeArgAt(0));
const Array& fun_arg_names =
Array::CheckedHandle(zone, arguments->NativeArgAt(1));
const Array& fun_args_desc = Array::Handle(
zone, ArgumentsDescriptor::NewBoxed(kTypeArgsLen, fun_arguments.Length(),
fun_arg_names));
const Object& result = Object::Handle(
zone, DartEntry::InvokeClosure(thread, fun_arguments, fun_args_desc));
if (result.IsError()) {
Exceptions::PropagateError(Error::Cast(result));
}
return result.ptr();
}
static bool ClosureEqualsHelper(Zone* zone,
const Closure& receiver,
const Object& other) {
if (receiver.ptr() == other.ptr()) {
return true;
}
if (!other.IsClosure()) {
return false;
}
const auto& other_closure = Closure::Cast(other);
const auto& func_a = Function::Handle(zone, receiver.function());
const auto& func_b = Function::Handle(zone, other_closure.function());
// Check that functions match.
if (func_a.ptr() != func_b.ptr()) {
// Non-implicit closures taken from different functions are not equal.
if (!func_a.IsImplicitClosureFunction() ||
!func_b.IsImplicitClosureFunction()) {
return false;
}
// If the closure functions are not the same, check the function's name and
// owner, as multiple function objects could exist for the same function due
// to hot reload.
if ((func_a.name() != func_b.name() || func_a.Owner() != func_b.Owner() ||
func_a.is_static() != func_b.is_static())) {
return false;
}
}
// Check that the delayed type argument vectors match.
if (receiver.delayed_type_arguments() !=
other_closure.delayed_type_arguments()) {
// Mismatches should only happen when a generic function is involved.
ASSERT(func_a.IsGeneric() || func_b.IsGeneric());
const auto& type_args_a =
TypeArguments::Handle(zone, receiver.delayed_type_arguments());
const auto& type_args_b =
TypeArguments::Handle(zone, other_closure.delayed_type_arguments());
if (type_args_a.IsNull() || type_args_b.IsNull() ||
(type_args_a.Length() != type_args_b.Length()) ||
!type_args_a.IsEquivalent(type_args_b, TypeEquality::kSyntactical)) {
return false;
}
}
if (func_a.IsImplicitClosureFunction() &&
func_b.IsImplicitClosureFunction()) {
if (!func_a.is_static()) {
// Check that the both receiver instances are the same.
const Context& context_a = Context::Handle(zone, receiver.context());
const Context& context_b = Context::Handle(zone, other_closure.context());
return context_a.At(0) == context_b.At(0);
}
} else if (func_a.IsGeneric()) {
// Additional constraints for closures of generic functions:
// (1) Different instantiations of the same generic closure
// with the same type arguments should be equal.
// This means that instantiated generic closures are not unique
// and equality of instantiated generic closures should not be
// based on identity.
// (2) Instantiations of non-equal generic closures should be non-equal.
// This means that equality of non-instantiated generic closures
// should not be based on identity too as it won't match equality
// after instantiation.
if ((receiver.context() != other_closure.context()) ||
(receiver.instantiator_type_arguments() !=
other_closure.instantiator_type_arguments()) ||
(receiver.function_type_arguments() !=
other_closure.function_type_arguments())) {
return false;
}
} else {
// Closures of non-generic functions are unique.
return false;
}
return true;
}
DEFINE_NATIVE_ENTRY(Closure_equals, 0, 2) {
const Closure& receiver =
Closure::CheckedHandle(zone, arguments->NativeArgAt(0));
GET_NATIVE_ARGUMENT(Instance, other, arguments->NativeArgAt(1));
ASSERT(!other.IsNull());
return Bool::Get(ClosureEqualsHelper(zone, receiver, other)).ptr();
}
DEFINE_NATIVE_ENTRY(Closure_computeHash, 0, 1) {
const Closure& receiver =
Closure::CheckedHandle(zone, arguments->NativeArgAt(0));
return Smi::New(receiver.ComputeHash());
}
} // namespace dart