-
Notifications
You must be signed in to change notification settings - Fork 64
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
Field get/set functions, type safety, and violating containment #239
Comments
Also So this is what is occurring (in the Vector3 case) to get an array containing the correctly ordered nodal information for an element:
So we're casting an array of (for a tet) 12 doubles to an array of 4 If the classes we use for nodal storage and retrieval is 'standard layout' (which is slightly less strict than being POD, but still has many restrictions), both of these issues I've identified actually do adhere to the standard. However we need to document this and make it explicit in the code. Since C++11 there has been See here for some discussion of standard layout versus POD. |
…implementing ability to get Element nodes for a packed field, restricting ElementOf<T> to be standard layout so the underlying data retrieval cast is valid (see issue #239)
So it turns out one of the variants of the functions to create This means that the downcast in the get/set-ter functions isn't valid for these elements since they are not of type This also means we cannot assert that we are able to cast the passed in Since in this case the This isn't going to happen any time soon though, so for now we either (1) can't have type protection on the get/set-ter functions, or (2) we need to require that fields implement both element constructor functions so we never create raw I'm going to exclude this from my work on complex -- which is very close to being ready to merge -- and leave it for later. |
Came across this again in the code... It's really bad that we are reinterpret casting For the type safety issue @wrtobin brought up we should profile how bad @mortezah you can assign me this issue. I'll put together the fix/timing hopefully sometime this week. |
So while working on PR #234 to support complex fields @cwsmith and I discussed the fact that the get/set(Scalar,Vector,Matrix) API functions are not type safe in that a Scalar/Vector/Matrix/Packed field can be passed to any of these functions as a Field*, which is then down-cast to the appropriate type. This conversion is well-defined in the case that the passed in Field* actually does point to a field of the appropriate sub-class and thus with the appropriate 'Value' type.
But
static_cast
doesn't check that the down-cast is appropriate:dynamic_cast
does do this check (and would work in this case because all of the Field sub-classes are polymorphic). So that would be the appropriate change to make. Of course that cast operation uses RTTI to ensure the result is actually a pointer to the correct type, but that adds overhead to these functions, which are definitely tight-loop functions, which I assume is why thestatic_cast
is what is currently used (though the internal call togetNodeValue
is still polymorphic so these functions can never be inlined/highly optimized, they will always require at least that one dynamic function dispatch.In order to no add any call overhead to these functions I will be adding a
PCU_DEBUG_ASSERT
against the value offield->getValueType()
to make things slighty safer and not add any overhead on optimized runs of any downstream codes, not that we ever expect this to be fast.Now the real weirdness and the real reason for the issue:
All of these getter/setter functions call
FieldOf<T>::get/setNodeValue()
which takes a pointer to the 'value' of the field, so forSCALAR
andPACKED
this is justdouble
but forVECTOR
andMATRIX
it isVector3
andMatrix3x3
respectively. Inside this function theT*
argument isreinterpret_cast<double*>
(orconst double*
for setting). This means the operation being performed is:So we're casting a pointer to an object to a pointer to a double. The
Vector3
andMatrix3x3
classes are both ultimately inherited fromcan::Array
. In the case ofVector3
, it makes some sense that the address of the object is also (apparently in general) the address of the array of double array inside of the object. In theMatrix3x3
case the array contains 3Vector3
objects, each of which I imagine when layed out in memory also just consist of the length 3 double arrays they contain, so this makes some sense too.However, this casting a pointer to an object to a pointer to the 'first' data member of an object is not covered by the standard and the result in general is undefined behavior. I'm certain the reason we've gotten away with this for so long is that it is the most sensible way for the compiler to put together the actual memory layout of the class, but it isn't defined this way by the standard, so we're making a huge assumption.
While these classes do have an inheritance hierarchy they are not polymorphic, so there is no function lookup table. But when there is a function lookup table, guess were it goes for each object of that class? Right at the start of the object in memory (technically it is implementation-define (like the address of the first data member is also)). So if we were to ever edit the
can::Array
,mth::Vector
, ormth::Matrix
classes and introduce a single virtual function (which we never will but nonetheless), this portion of the API instantly breaks.Further (as noted), this violated encapsulation as we're directly accessing underlying protected data of the class. Granted in this case is is read-only which is slightly more forgivable, but nonetheless is is still an issue.
The only fix I can think of for this issue is to add an explicit cast operator to the
Vector
class:this takes care of that issue. The
Matrix
class is a bigger problem since it is a container of multipleVector
s... I don't really have an elegant solution for this one.The text was updated successfully, but these errors were encountered: