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

Dervived classes with optional fields generate invalid code #202

Open
larws opened this issue Jan 16, 2025 · 11 comments
Open

Dervived classes with optional fields generate invalid code #202

larws opened this issue Jan 16, 2025 · 11 comments

Comments

@larws
Copy link

larws commented Jan 16, 2025

The template code of DerivedClassWithOptionalFields.cs is invalid since in the code from Line 60 to Line 82 the EncodingMask is not correctly taken into account. In case the base class has fields that need to be serialized or deserialized the EncodingMask has to be calculated and serialized/deserialized before any fields a serialized/deserialized. With the current template setup this is not possible.

The order of the fields and encoding mask needs to be in valid order when generating code for derived classes with optional fields.

@larws
Copy link
Author

larws commented Jan 16, 2025

@opcfoundation-org This issue was introduced with the PR #193 and the relating issue #192. I currently don't have a solution and not the capacity to spend any time on that issue, thus I am writing the issue so it won't be forgotten.

@opcfoundation-org
Copy link
Contributor

opcfoundation-org commented Jan 16, 2025

The EncodingMask is a property that has to be set before the Encode is called.
This means the base.Encode() call correctly encodes the EncodingMask and the bug is the duplicate EncodingMask write.
Simply deleting Line 65 should fix the issue.

@larws
Copy link
Author

larws commented Jan 16, 2025

Deleting Line 65 won't solve the issue, since the derived class might have optional fields as well and thus needs to contribute to the EncodingMask property. The only way I can currently think of is providing a hook to calculate the encoding mask over the whole class hierarchy and call it from the base class, but this needs to be carefully added to check any edge cases.

@opcfoundation-org
Copy link
Contributor

The derived class re-declares the enumeration but does not redeclare the property.
So writing the EncodingMask in the base.Encode() call will correctly set the bits for the derived fields.
Note the datatype of the EncodingMask is uint and not the enum because this was the expected behavoiur.

@larws
Copy link
Author

larws commented Jan 17, 2025

How would the case be handled where the base class has no optional fields and thus no EncodingMask property?

@opcfoundation-org
Copy link
Contributor

The enumeration for the fields is always re-declared for each subtype even if no new optional fields are added.
So there is no difference for that case.

@larws
Copy link
Author

larws commented Jan 17, 2025

I think we are talking about different things. Let me try to build an example.

OPC-UA data type model:

Person { string: Name, int: Age } with the subtype Student : Person { string?: School } which has an optional property School. The generated code would look something like this, if I understand you correctly:

class Person
{
...
    public virtual void Encode(IEncoder encoder)
    {
        encoder.PushNamespace("School");

        encoder.WriteString("Name", Name);
        encoder.WriteInt("Age", Age);

        encoder.PopNamespace();
    }
}
class Student: Person
{
...
    public virtual void Encode(IEncoder encoder)
    {
        base.Encode(encoder);
        encoder.PushNamespace("School");

        if ((EncodingMask & StudentFields.School) != 0) encoder.WriteString("School", School);

        encoder.PopNamespace();
    }
}

In this case the EncodingMask property will not be written into the stream which would result into an invalid encoding. Am I missing something?

@opcfoundation-org
Copy link
Contributor

The first Structure with optional fields must a direct subtype of Structure.

See:
https://reference.opcfoundation.org/Core/Part3/v105/docs/8.49

Structure | 0 | A Structure without optional fields where none of the fields allow subtyping

I realize now the text is ambiguous but the requirement 'Structure without optional fields' was supposed to imply it has to be true for all fields added by subtypes because this statement would no longer be true for the base type.

@larws
Copy link
Author

larws commented Jan 20, 2025

I think this is an interesting way of interpreting that sentence in the specification.

In my opinion viewing a type representing data through its basetype interface I have no way of knowing which fields are added through any types deriving from them, thus this statement would not be affected if a subtype adds any new values which might be optional.

Another difficulty with this interpretation is that it differs from the behavior of the modeling tools around since they allow modeling structures this way. So nodesets with this type of modeling are around and should be correctly handled by the model compiler in my opinion.

@opcfoundation-org
Copy link
Contributor

This needs to be discussed in the WG.

While you are correct from a data modelling perspective. The absence of a clear statement one way or the other means different implementors will make different decisions. The WG needs to determine which clarification is the least likely to lead to IOP issues.

@opcfoundation-org
Copy link
Contributor

Specification will be clarified: https://mantis.opcfoundation.org/view.php?id=10110

The ModelCompiler will allow this scenario.

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

No branches or pull requests

2 participants