-
Notifications
You must be signed in to change notification settings - Fork 722
Improve encapsulation of logic relating to Layer attachment to Packet. #2004
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
base: dev
Are you sure you want to change the base?
Conversation
… to a Layer's ownership from regular fields.
| // Should this really always delete m_Data? What if the layer is attached to a packet? | ||
| if (m_Data != nullptr) | ||
| delete[] m_Data; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See comment? This would cause an error if called on a Layer that does not own its data span.
| else | ||
| { | ||
| layer->m_Packet = nullptr; | ||
| layer->m_AllocationInfo.detach(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line in particular created a subtle bug in the old implementation.
If a layer is originally owned by the packet instance, and detached, the m_IsAllocatedInPacket flag was not zeroed. If the layer has then been attached to another Packet. The attached layer was considered as if managed by the new packet even if attached with addLayer(layer, false) which should not transfer ownership of the layer object.
| /// | ||
| /// If 'false', the Layer object is considered unmanaged and the user is responsible for freeing it. | ||
| /// This is commonly the case for layers created on the stack and attached to a Packet. | ||
| bool managedByPacket = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe ownedByPacket is a more descriptive name? 🤔
| PTF_ASSERT_TRUE(packetWithoutTunnel.addLayer(vxlanEthLayer, true)); | ||
| PTF_ASSERT_TRUE(packetWithoutTunnel.addLayer(vxlanIP4Layer, true)); | ||
| PTF_ASSERT_TRUE(packetWithoutTunnel.addLayer(vxlanIcmpLayer, true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or should we keep the test as is, but correctly delete the layers afterwards?
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## dev #2004 +/- ##
==========================================
+ Coverage 83.41% 83.45% +0.03%
==========================================
Files 311 311
Lines 55019 54576 -443
Branches 11816 11806 -10
==========================================
- Hits 45892 45544 -348
- Misses 7852 8199 +347
+ Partials 1275 833 -442
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
|
@Dimi1010 I'm not sure why replacing 2 protected members ( |
@seladb It mitigates the risk of this issue #2004 (comment) which was a bug that can be hard to track and easy to happen and completely broke the memory ownership model. Having them in the same struct indicates that the two variables are much more tightly related compared to let's say, If the allocation info needs to be updated, it is more likely to be done through behaviour actions ( IMO, this is easier to understand and less bug-prone at the point of use, than having to remember to flip N variables manually every time the operation needs to be performed.
Protected data make it a pain to ensure invariants in the base classes of an inheritance hierarchy. That is especially true for large hierarchies like If it was Making it private with RO accessors (if RO access is needed), changes the situation from "It can happen, but shouldn't." to "It can't happen". Removing the need to scan every code change for if a bug of such nature was introduced. PS: |
There are very few places that need to update these members, and most (if not all) of them reside in the
I agree that layers shouldn't deal with the allocation logic, and I'm pretty sure none of them do. In practice, they never need to unless there's a very special use case. I guess we can leave the RO method although I don't think it's a real pain for people writing protocol parsers - I don't recall a case where I asked someone not to change these members... |
It wouldn't have been that simple to find. I found it only after I grouped the variables and a test suddenly started memory leaking due to being incorrect. It was attaching the layer without ownership and then not deleting the layer. Everything still appeared to run fine because the packet deleted it erroneously. Also, IMO that is only treating the symptom. In memory ownership schemes, generally keeping things simple at the point of use is key to prevent errors. Having to manually remember which variables to flip is not simple at the point of use, even if its only a few places. Having the complex actions encapsulated in methods is better due to explicit requirement for all parameters to be provided before executing the action.
In practice, if the fields aren't needed by the derived classes then they shouldn't be exposed to them. I don't think we will have any special use cases where a derived class will need to change its owned packet from its internals. The only need they have for the packet is to forward the pointer to the next layer which is RO operation. |
Summary
This PR intended to improve the encapsulation and clarity of Layer field data. As per the C++ core guidelines
protecteddata fields are generally undesired as they represent difficulty in maintaining base class invariants across derived classes.The main object of this PR is the introduction of a new helper struct
LayerAllocationInfoin the internal namespace, to hold allocation information for Layer objects and provide behavior helper methods. The helper structure replaces is contained as a field in aLayerto represent its current allocation state.LayerAllocationInfoand chosen fields.The information structure contains 2 members:
Packet* attachedPacket, replacingm_Packetpointer insideLayer.bool managedByPacket, replacingm_isAllocatedInPacketflag insideLayer.The replacements were chosen due to their relevancy to the allocation behaviour of
Layerand little else. Those fields were previously intermixed with other fields in theLayerobject causing confusion when determining their use and scope.attachedPacket(previouslym_Packet) is exclusively used for indication if the layer's data is owned by a packet and to forward requests for structural data changes to the underlying packet buffer, if available. The new name was chosen to better represent that use.managedByPacket(previouslym_IsAllocatedInPacket) is exclusively used as flag to determine if theLayerobject's lifecycle is managed by thePacketinstance it is attached to. The field intricately tied to the state ofattachedPacketand as such is included to the helper structure.The new fields include extensive documentation regarding their use, improving the maintainability of the code. ( Think about the you in 5 months reading it 🙂 )
Behaviour methods instead of direct field mutation
The new helper structure adds two behavior methods:
attachPacket(Packet* packet, bool managed, bool force = false): This method encapsulates the behavior of attaching a layer to a Packet instance. It allows aPacketinstance attaching aLayerto itself to update the information in one step instead of manually having to directly updateprivatefields to theLayerinstance. The method also provides integrated sanity checks if aLayeris being attached to aPacketinstance twice.detach(): Encapsulates the behaviour of detaching aLayerfromPacket. It allows aPacketinstance detaching aLayerto inform it of its new state in one step.Protected data accessors and private fields
The new
LayerAllocationInfostructure is held as a private field insideLayer. The change improves data integrity as the allocation information (packet or managed flag) is unavailable for accidental modification by derived classes.To keep existing behaviour of a
Layerinstance forwarding its attachment to any other instance it constructs during parsing, a new protected accessorgetAttachedPacket()has been added and instances of direct use ofm_Packetare replaced by it.Bug Fixes
m_IsAllocatedInPacketflag when detaching a layer. If said layer is attached to a new packet instance, the old implementation disregarded the ownership flag and considered the layer instance as owned by the packet, due to the layer flag being set from the previous packet.