Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mapping between .Net objects and JSReference should not allow to match objects of different types. #156

Closed
wants to merge 2 commits into from

Conversation

dmitriyse
Copy link

@dmitriyse dmitriyse commented Sep 28, 2023

The problem can be reproduced as follows:

  1. Creating a class (let name it MyObj) with overridden Object.Equals that matches different instances
  2. Creating instance A of MyObj
  3. Creating instance B of MyObj which MyObj.Equals matches to instance A
  4. Passing instance A to JS (for example as a return from a Method)
  5. Passing instance B to JS
  6. Problem: JS will receive instance A two times, and it's no any way to pass instance B to JS

This fix implements object comparison that doesn't match objects of different types, even if the Equals(object) method matches another type.
Plus, it allows the replacement of the comparer if, for some reason, the default comparer implementation does not work well with an Equal(object) implementation of some type.

@dmitriyse
Copy link
Author

@microsoft-github-policy-service agree

@dmitriyse dmitriyse changed the title Mapping between .Net objects and JSReference should references and ignore Equals overrides. Mapping between .Net objects and JSReference should ignore Equals overrides. Sep 28, 2023
@jasongin
Copy link
Member

jasongin commented Oct 6, 2023

@dmitriyse Thanks for the PR! I understand the issue this is trying to fix, and it makes sense for reference types.

However I'm not sure about value types (structs). There is a test failure when checking JS equality of Version, which is a struct:

Failed Microsoft.JavaScript.NodeApi.Test.HostedClrTests.Test(id: "napi-dotnet/dynamic_invoke") [767 ms]
  Error Message:
   Assert.Fail(): node:assert:1[25](https://github.com/microsoft/node-api-dotnet/actions/runs/6336476172/job/17479606049?pr=156#step:12:26)
  throw new AssertionError(obj);
  ^
AssertionError [ERR_ASSERTION]: Values have same structure but are not reference-equal

You can run just that test case with the command:

dotnet test -f net8.0 --filter "DisplayName~dynamic_invoke"

.NET structs are boxed when wrapped by JS, or when stored or compared in that dictionary. So every time a value is boxed it gets a new object instance, meaning reference equality doesn't work.

Arguably since Version is a .NET value-type, the JS equality check should maybe be checking only for value equality and not reference equality. But there are no "value types" in JS, so I think the expectation of a JS developer would be that an object that is passed through .NET and back to JS should still be the same JS object instance. What do you think?

More broadly, I'm curious: what is your interest in this project, and how do you plan to use it?

@dmitriyse
Copy link
Author

Hi @jasongin,
Thank you for the feedback and tips for debugging.

I am working on the bridge for the testing framework that propagates FlaUI API to Node.JS

I can implement a custom comparison that falls back to the structural Equality for:

  • Value Types
  • C# Strings

I think it's hard to find a solution for object mapping that doesn't have any side effects. Once we discover some unwanted side effects, we will try to improve the solution.

I will prepare the update of this PR in the next couple of days.

@jasongin
Copy link
Member

jasongin commented Oct 6, 2023

I am working on the bridge for the testing framework that propagates FlaUI API to Node.JS

Cool! Feel free to open issues about any challenges you encounter in doing that. And I would love to see a demo when you have something ready to share.

I can implement a custom comparison that falls back to the structural Equality for: Value Types, C# Strings

That sounds like it should work well.

@dmitriyse dmitriyse changed the title Mapping between .Net objects and JSReference should ignore Equals overrides. Mapping between .Net objects and JSReference should not allow to match objects of different types. Jan 21, 2024
@dmitriyse
Copy link
Author

@jasongin, can you please review the updated implementation of this PR?
I have changed the approach to fix the problem. Mapping logic will not return unexpected types to JS, as different types will never match some existing mapping.


// We should deliberately make different types not equal to avoid type definition mismatch.
// Object of type B that matched through A.Equal(B) can be accessed in JS through description of type A, which is wrong.
if (x.GetType() != y.GetType())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right, and is different from what was discussed. It will return the wrong result for two (equal) instances of the same class, as in the original problem scenario you described.

If x is a reference type then we want to use reference equality, as in the original iteration of this PR. If x is a (boxed) value-type then we want to use value equality. (Value equality should fail if the values are different types.)

I think this should work, but it might need some testing:

return x is ValueType ? x.Equals(y) : Object.ReferenceEquals(x, y);

@jasongin
Copy link
Member

jasongin commented Mar 24, 2024

Superseded by PR #247

The implementation in the newer PR is basically the same as the first iteration of this one, though I added tests.

I apologize for my confusion in the discussion above. I had been thinking Version is a struct, but it is actually a class. So I had misinterpreted the failed test case related to marshalling Version. The object map is not used at all for structs, so there was no need to handle value-types in its equality comparer.

@jasongin jasongin closed this Mar 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants