Page MenuHomePhabricator

ImageReadAccessor: Access violation through operator=()
Closed, ResolvedPublic

Description

Using a code snippet like this

{

mitk::ImagePixelReadAccessor<mitk::ScalarType,3> accessor =
mitk::ImagePixelReadAccessor<mitk::ScalarType,3>(image);

}

will cause an access violation as soon as we leave scope.

Reason:
The default copy operator is not explicitly deactivated, leading to the left side accessor to be constructed be mere copying. Thus no ImageReadAccessor:: OrganizeReadAccess() is called.

The Destructor of the right side accessor (RSA) clears m_Image->m_Readers.

Now we have an accessor that is not registered and an image with an empty readers vector.

If we leave the scope, the RSA destructor tries to remove the reader.

The next issue comes with line 74-75 (mitkImageReadAccessor.h)

74: std::vector<ImageAccessorBase*>::iterator it = std::find(m_Image->m_Readers.begin(),m_Image->m_Readers.end(),this);
75: m_Image->m_Readers.erase(it);

Now in line 75 erase is called with "it == m_Image->m_Readers.end()"

Calling std::vector::erase on the end-iterator is undefined behavior (which causes an access violation on windows platforms).

Proposals:

  1. Add check to ~ImageReadAccessor(), to avoid undefined behavior.

std::vector<ImageAccessorBase*>::iterator it = std::find(m_Image->m_Readers.begin(),m_Image->m_Readers.end(),this);
if (it != m_Image->m_Readers.end())
{

m_Image->m_Readers.erase(it);

}

  1. Explicitly remove operator=() to avoid this wrong usage by design

or implement it explicitly and correctly ;)

Event Timeline

bug fix proposal added via git hub pull request.

Thank you for your helpful contribution.

As designated in our concept, the copy constructor and operator=() are permitted.
Therefore, the compilation of your code snippet should fail.

Unfortunately, the copy constructor and the operator=() were not disabled. The upcoming bug-branch fixes this problem.

@Ralf Floca: Regarding your first proposal, the situation (it == m_Image->m_Readers.end()) must not occur. Adding an if-condition might leave the accessor in undefinded state with unpredictable side-effects.

User goerres has pushed new remote branch:

bug-17505-DisableCopyConstructorAndOperator=

(In reply to Joseph Görres from comment #2)

As designated in our concept, the copy constructor and operator=() are
permitted.

Sorry, not permitted.

(In reply to Joseph Görres from comment #2)

@Ralf Floca: Regarding your first proposal, the situation (it ==
m_Image->m_Readers.end()) must not occur. Adding an if-condition might leave
the accessor in undefinded state with unpredictable side-effects.

Thank you for the quick response. What I do not understand is why the if condition should raise the chances to have an undefined state.

As far as my knowledge goes we could think of two scenarios (1) normal / (2) some exception.
For (1): We would go into the if block if the element realy is in the vector and remove it. Or we would skipp the block and avoid an std::out_of_range and undefined behavior, because we would have tried to erase an not existing element of the vector.
So for (1) I can only see the avoidance of undefined states but not the introducing of new risks.

For (2): We introduced three new operations (a) dereferencing "m_Image->", (b) function call "m_Readers.end()" and (c) Operator "!=" that could introduce new risks.
(a) if this would be a problem the destructor would gone into an invalid state earlier by the remaining old code. So no new risk introduced. And if the Pointer is valid, "->" has a no throw guarantee.
(b) end() has a no throw guaranteed for the std::vector with such an template parameter (http://www.cplusplus.com/reference/iterator/end/). So no additional risk. This part will never fail.
(c) As far as I know != is save for the default random access iterator. But here I may be wrong, but currently I assume it.
So for (2) we introduced, as far as my knowledge goes, no new risks for getting an exception and thus into undefined state.

If I have missed some thing or haven't digged deep enough into your design(which is surely the case) in order to understand the context and interrelations, I would be very glad if you could explain it to me. Thank you very much.

(In reply to Ralf Floca from comment #5)

So for (1) I can only see the avoidance of undefined states but not the
introducing of new risks.

Let me put it in other words:

If at that point, an expected Accessor was missing, the whole concept would be inconsistent. This situation must not occur. With your code example and the problem of a non-disabled default copy constructor, it was possible to bring the Accessor into such an inconsistent state. In this case, the program rather crashes or throws an exception and allows us debugging. Otherwise, the Accessor doesn't follow our concept anymore. The program might crash at another point (side-effect) where we can spend a lot of time searching the actual reason.

(In reply to Joseph Görres from comment #6)

The program
might crash at another point (side-effect) where we can spend a lot of time
searching the actual reason.

Point taken. I have missunderstood you first comment as a remark that this code snippet would introduce new risks for invalid states and not understood it as a remark that you skipp it because of the design descision "we don't take care about it there, because by design it should not happen and if it does, it should happen there". This is faire enough.

But going this road, have you thought about putting

assert(it != m_Image->m_Readers.end())

before the erase() to ease the debugging if such an invalid situation occurs?

Thanks for taken the time to clarify and discuss the topic.

[1e0924]: Merge branch 'bug-17505-DisableCopyConstructorAndAssignmentOperator'

Merged commits:

2014-03-26 16:44:39 Joseph Görres [0a4afc]
disabled copy constructor and operator= in pixel- and non-pixel- as well as read- and write- accessors