mirror of
https://github.com/enso-org/enso.git
synced 2024-11-26 17:06:48 +03:00
Avoid StackOverflow when comparing unknown foreign objects (#7780)
Closes #7677 by eliminating the _stackoverflow execption_. In general it seems _too adventurous_ to walk members of random foreign objects. There can be anything including cycles. Rather than trying to be too smart in these cases, let's just rely on `InteropLibrary.isIdentical` message. # Important Notes Calling `sort` on the `numpy` array no longer yields an error, but the array isn't sorted - that needs a fix on the Python side: https://github.com/oracle/graalpython/issues/354 - once it is in, the elements will be treated as numbers and the sorting happens automatically (without any changes in Enso code).
This commit is contained in:
parent
5c198145a1
commit
3b790606e1
@ -370,50 +370,10 @@ public abstract class EqualsComplexNode extends Node {
|
||||
@Shared("typesLib") @CachedLibrary(limit = "10") TypesLibrary typesLib,
|
||||
@Shared("equalsNode") @Cached EqualsNode equalsNode,
|
||||
@Shared("hostValueToEnsoNode") @Cached HostValueToEnsoNode valueToEnsoNode) {
|
||||
try {
|
||||
Object selfMembers = interop.getMembers(selfObject);
|
||||
Object otherMembers = interop.getMembers(otherObject);
|
||||
assert interop.getArraySize(selfMembers) < Integer.MAX_VALUE
|
||||
: "Long array sizes not supported";
|
||||
int membersSize = (int) interop.getArraySize(selfMembers);
|
||||
if (interop.getArraySize(otherMembers) != membersSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check member names
|
||||
String[] memberNames = new String[membersSize];
|
||||
for (int i = 0; i < membersSize; i++) {
|
||||
String selfMemberName = interop.asString(interop.readArrayElement(selfMembers, i));
|
||||
String otherMemberName = interop.asString(interop.readArrayElement(otherMembers, i));
|
||||
if (!equalsNode.execute(selfMemberName, otherMemberName)) {
|
||||
return false;
|
||||
}
|
||||
memberNames[i] = selfMemberName;
|
||||
}
|
||||
|
||||
// Check member values
|
||||
for (int i = 0; i < membersSize; i++) {
|
||||
if (interop.isMemberReadable(selfObject, memberNames[i])
|
||||
&& interop.isMemberReadable(otherObject, memberNames[i])) {
|
||||
Object selfMember =
|
||||
valueToEnsoNode.execute(interop.readMember(selfObject, memberNames[i]));
|
||||
Object otherMember =
|
||||
valueToEnsoNode.execute(interop.readMember(otherObject, memberNames[i]));
|
||||
if (!equalsNode.execute(selfMember, otherMember)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (interop.isIdentical(selfObject, otherObject, interop)) {
|
||||
return true;
|
||||
} catch (UnsupportedMessageException
|
||||
| InvalidArrayIndexException
|
||||
| UnknownIdentifierException e) {
|
||||
throw new IllegalStateException(
|
||||
String.format(
|
||||
"One of the interop objects has probably wrongly specified interop API "
|
||||
+ "for members. selfObject = %s ; otherObject = %s",
|
||||
selfObject, otherObject),
|
||||
e);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -549,6 +509,9 @@ public abstract class EqualsComplexNode extends Node {
|
||||
if (interop.isTime(object)) {
|
||||
return false;
|
||||
}
|
||||
if (interop.hasHashEntries(object)) {
|
||||
return false;
|
||||
}
|
||||
return interop.hasMembers(object);
|
||||
}
|
||||
|
||||
|
@ -49,11 +49,16 @@ spec = Test.group "Python Examples" <|
|
||||
main = dir/"src"/"Main.enso"
|
||||
main.exists . should_be_true
|
||||
code = """
|
||||
from Standard.Base import all
|
||||
|
||||
foreign python random_array s = """
|
||||
import numpy
|
||||
return numpy.random.normal(size=s)
|
||||
|
||||
main = random_array 10
|
||||
main =
|
||||
arr = random_array 10
|
||||
vec = arr.to_vector.sort
|
||||
[ arr, vec ]
|
||||
|
||||
code . write main on_existing_file=Existing_File_Behavior.Overwrite
|
||||
|
||||
|
@ -0,0 +1,22 @@
|
||||
package org.enso.base_test_helpers;
|
||||
|
||||
public final class IntHolderEquals {
|
||||
public final int value;
|
||||
public final Integer boxed;
|
||||
|
||||
public IntHolderEquals(int value) {
|
||||
this.value = value;
|
||||
this.boxed = value;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof IntHolderEquals other) {
|
||||
return value == other.value;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -760,13 +760,13 @@ type_spec name alter = Test.group name <|
|
||||
alter [T.Value 1 2, T.Value 3 3, T.Value 1 2] . distinct . should_equal [T.Value 1 2, T.Value 3 3]
|
||||
alter [T.Value 1 2, T.Value 3 3, T.Value 1 2, Nothing] . distinct . should_equal [T.Value 1 2, T.Value 3 3, Nothing]
|
||||
alter [Nothing, T.Value 1 2, T.Value 3 3, T.Value 1 2, Nothing] . distinct . should_equal [Nothing, T.Value 1 2, T.Value 3 3]
|
||||
alter [T.Value 1 2, Date.new hours=3] . distinct . should_equal [T.Value 1 2, Date.new hours=3]
|
||||
alter [T.Value 1 2, Date.new year=1973] . distinct . should_equal [T.Value 1 2, Date.new year=1973]
|
||||
|
||||
Test.specify "should return a vector containing only unique elements up to some criteria" <|
|
||||
alter [Pair.new 1 "a", Pair.new 2 "b", Pair.new 1 "c"] . distinct (on = _.first) . should_equal [Pair.new 1 "a", Pair.new 2 "b"]
|
||||
|
||||
Test.specify "should be able to sort a heterogenous vector" <|
|
||||
arr = [ 1, 1.3, "hi", Date.today, Date_Time.now, [ 0 ] ]
|
||||
arr = alter [ 1, 1.3, "hi", Date.today, Date_Time.now, [ 0 ] ]
|
||||
(arr.sort on=(.to_text) . map .to_text) . should_equal (arr.map .to_text . sort)
|
||||
(arr.sort on=(_.to_text) . map .to_text) . should_equal (arr.map .to_text . sort)
|
||||
(arr.sort on=(x-> x.to_text) . map .to_text) . should_equal (arr.map .to_text . sort)
|
||||
|
@ -3,8 +3,11 @@ from Standard.Base import all
|
||||
from Standard.Test import Test, Test_Suite
|
||||
import Standard.Test.Extensions
|
||||
|
||||
polyglot java import java.nio.file.Path as JavaPath
|
||||
polyglot java import java.math.BigInteger as Java_Big_Integer
|
||||
polyglot java import java.nio.file.Path as Java_Path
|
||||
polyglot java import java.util.Random as Java_Random
|
||||
polyglot java import org.enso.base_test_helpers.IntHolder
|
||||
polyglot java import org.enso.base_test_helpers.IntHolderEquals
|
||||
|
||||
type CustomEqType
|
||||
C1 f1
|
||||
@ -110,6 +113,24 @@ foreign js js_true = """
|
||||
foreign js js_text_foo = """
|
||||
return 'foo'
|
||||
|
||||
foreign js js_json a b = """
|
||||
return JSON.parse(`
|
||||
{
|
||||
"a" : ${a},
|
||||
"b" : ${b}
|
||||
}`);
|
||||
|
||||
foreign js js_map a b = """
|
||||
var m = new Map()
|
||||
m.set("a", a)
|
||||
m.set("b", b);
|
||||
return m
|
||||
|
||||
foreign js js_obj a b = """
|
||||
var m = {};
|
||||
m.a = a;
|
||||
m.b = b;
|
||||
return m;
|
||||
|
||||
spec =
|
||||
Test.group "Operator ==" <|
|
||||
@ -158,8 +179,8 @@ spec =
|
||||
((CustomEqType.C1 0) == (CustomEqType.C2 7 3)).should_be_false
|
||||
|
||||
Test.specify "should dispatch to equals on host values" <|
|
||||
path1 = JavaPath.of "home" "user" . resolve "file.txt"
|
||||
path2 = JavaPath.of "home" "user" "file.txt"
|
||||
path1 = Java_Path.of "home" "user" . resolve "file.txt"
|
||||
path2 = Java_Path.of "home" "user" "file.txt"
|
||||
(path1 == path2).should_be_true
|
||||
path3 = path1.resolve "subfile.txt"
|
||||
(path3 == path2).should_be_false
|
||||
@ -203,5 +224,59 @@ spec =
|
||||
dupl_tree = tree.deep_copy
|
||||
Test.with_clue "Seed sed to 42" (tree == dupl_tree).should_be_true
|
||||
|
||||
Test.specify "partially applied constructors aren't == " <|
|
||||
f1 = CustomEqType.C2 10
|
||||
f2 = CustomEqType.C2 10
|
||||
f1==f2 . should_be_false
|
||||
|
||||
Test.group "Polyglot Operator ==" <|
|
||||
Test.specify "should not try to compare members" <|
|
||||
x = IntHolder.new 5
|
||||
y = IntHolder.new 5
|
||||
z = IntHolder.new 3
|
||||
x==z . should_be_false
|
||||
x==y . should_be_false
|
||||
y==y . should_be_true
|
||||
x==x . should_be_true
|
||||
z==z . should_be_true
|
||||
|
||||
Test.specify "should not try to compare members in JS object" <|
|
||||
x = js_obj 5 3
|
||||
y = js_obj 5 3
|
||||
z = js_obj 3 5
|
||||
x==z . should_be_false
|
||||
x==y . should_be_false
|
||||
y==y . should_be_true
|
||||
x==x . should_be_true
|
||||
z==z . should_be_true
|
||||
|
||||
Test.specify "should invoke equals" <|
|
||||
x = IntHolderEquals.new 5
|
||||
y = IntHolderEquals.new 5
|
||||
z = IntHolderEquals.new 3
|
||||
x==z . should_be_false
|
||||
x==y . should_be_true
|
||||
y==y . should_be_true
|
||||
x==x . should_be_true
|
||||
z==z . should_be_true
|
||||
|
||||
Test.specify "should compare big integer" <|
|
||||
x = Java_Big_Integer.new "54024430107564432"
|
||||
y = Java_Big_Integer.new "54024430107564432"
|
||||
x==y . should_be_true
|
||||
|
||||
Test.specify "JSON is found different" <|
|
||||
x = js_json 10 5
|
||||
y = js_json 10 5
|
||||
x==y . should_be_false
|
||||
|
||||
Test.specify "Identical JSON is found equal" <|
|
||||
x = js_json 10 5
|
||||
x==x . should_be_true
|
||||
|
||||
Test.specify "JavaScript Map is found same" <|
|
||||
x = js_map 10 5
|
||||
y = js_map 10 5
|
||||
x==y . should_be_true
|
||||
|
||||
main = Test_Suite.run_main spec
|
||||
|
Loading…
Reference in New Issue
Block a user