Merge lp:~renatofilho/ubuntu-filemanager-app/fix-network-crash into lp:ubuntu-filemanager-app
- fix-network-crash
- Merge into trunk
Status: | Needs review | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~renatofilho/ubuntu-filemanager-app/fix-network-crash | ||||
Merge into: | lp:ubuntu-filemanager-app | ||||
Diff against target: |
196 lines (+47/-4) 7 files modified
src/plugin/folderlistmodel/diriteminfo.cpp (+19/-0) src/plugin/folderlistmodel/diriteminfo.h (+6/-0) src/plugin/folderlistmodel/iorequestworker.cpp (+7/-0) src/plugin/folderlistmodel/location.cpp (+0/-1) src/plugin/folderlistmodel/location.h (+0/-1) src/plugin/folderlistmodel/networklistworker.cpp (+10/-1) src/plugin/folderlistmodel/networklistworker.h (+5/-1) |
||||
To merge this branch: | bzr merge lp:~renatofilho/ubuntu-filemanager-app/fix-network-crash | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jenkins Bot | continuous-integration | Needs Fixing | |
Renato Araujo Oliveira Filho (community) | Disapprove | ||
Carlos Jose Mazieri | Needs Information | ||
Review via email: mp+314870@code.launchpad.net |
Commit message
Make sure that NetworkListWorker get invalidated if the parent DirItemInfo was destroyed.
App was crashing because DirItemInfo was destroyed and NetworkListWorker was trying to use an invalid pointer.
Description of the change
- 587. By Renato Araujo Oliveira Filho
-
Fix grammar.
Carlos Jose Mazieri (carlos-mazieri) wrote : | # |
Renato,
The bug description appears to say that it always crashes.
Can you confirm that it always crashes or it happens under some special condition?
If it happens under special condition can you describe that scenario?
Renato Araujo Oliveira Filho (renatofilho) wrote : | # |
> Renato,
>
> The bug description appears to say that it always crashes.
>
> Can you confirm that it always crashes or it happens under some special
> condition?
>
> If it happens under special condition can you describe that scenario?
It always crash for me.
Carlos Jose Mazieri (carlos-mazieri) wrote : | # |
Does it crash on both phone and desktop or just phone?
Carlos Jose Mazieri (carlos-mazieri) wrote : | # |
OK, Let's have a review on it,
1. perhaps the slot onParentDestroyed() on location.h lines 94-96 is not being used.
2. on line 38 from networklistwork
the default value for 'parent' is 0 on networklistworker.h
38: parent-
3. are you sure the code from networklistwork
the default value for 'parent' is 0 on networklistwork
m_parent is set to 0 and the "m_parent != 0" is checked on line 61.
57: if (!m_parent)
58: return netContent;
61: bool is_parent_
if you want keep lines 57-58, please change to "if (m_parent != 0)" as m_parent is not a boolean type.
4. can you explain why you removed the call to "refreshInfo()" from Location:
on location.cpp line 329?
I see that both Location:
this is the 'parent' used in NetworkListWorker class on the second thread.
That may be the real problem, perhaps just making an attribution into 'm_info'
from the temporary item could solve the entire problem, see a grep output:
./
./
./
./
./
./
Would you consider (adding this) or reconsider for this item #4 only?
Pat McGowan (pat-mcgowan) wrote : | # |
It always crashes and on both phone and desktop
Renato Araujo Oliveira Filho (renatofilho) wrote : | # |
Thanks for reviewing it.
> OK, Let's have a review on it,
>
> 1. perhaps the slot onParentDestroyed() on location.h lines 94-96 is not being
> used.
REMOVED
>
> 2. on line 38 from networklistwork
> the default value for 'parent' is 0 on networklistworker.h
>
> 38: parent-
FIXED
>
> 3. are you sure the code from networklistwork
> necessary?
> the default value for 'parent' is 0 on networklistwork
> onParentDestroyed() is called
> m_parent is set to 0 and the "m_parent != 0" is checked on line 61.
>
> 57: if (!m_parent)
> 58: return netContent;
>
> 61: bool is_parent_
> m_parent-
>
> if you want keep lines 57-58, please change to "if (m_parent != 0)" as
> m_parent is not a boolean type.
This is not necessary (REMOVED)
>
> 4. can you explain why you removed the call to "refreshInfo()" from
> Location:
> on location.cpp line 329?
>
> I see that both Location:
> the 'm_info',
> this is the 'parent' used in NetworkListWorker class on the second thread.
> That may be the real problem, perhaps just making an attribution into
> 'm_info'
> from the temporary item could solve the entire problem, see a grep output:
>
> ./src/plugin/
> delete m_info;
> ./src/plugin/
> m_info;
> ./src/plugin/
> ./src/plugin/
> ./src/plugin/
> ./src/plugin/
>
> Would you consider (adding this) or reconsider for this item #4 only?
We not not have a pointer to the temporary object (NetworkWorker) from Location class. Or do you have a way to access it?
- 588. By Renato Araujo Oliveira Filho
-
Remove unused code.
Check for parent before use it.
Carlos Jose Mazieri (carlos-mazieri) wrote : | # |
Renato,
by temporary objects I mean a temporary DirItemInfo created in Location?? classes, example:
void Location:
{
if (m_info)
{
DirItemInfo *item = newItemInfo(
delete m_info;
m_info = item;
}
}
Would be changed by:
void Location:
{
if (m_info)
{
DirItemInfo *item = newItemInfo(
*m_info = *item; // current m_info item receives current information
delete item;
}
}
I am not sure the operator "=" works properly for all DirItemInfo descendant classes.
Carlos Jose Mazieri (carlos-mazieri) wrote : | # |
Another and easy approach would be if the class NetworkListWorker receive again a const DirItemInfo *parent, but sets m_parent as its own instance using the operator '=', in this case it never gets deleted outside the worker thread.
Suppose the code changed:
NetworkListWork
DirListWork
m_dirIterat
m_mainItemI
m_parent(0)
{
if (parent != 0)
{
m_parent = new UrlItemInfo(); // UrlItemInfo is for remote
*m_parent = *parent; // not sure it works, needs be reviewed
}
mLoaderType = NetworkLoader;
// this would not be necessary and can be removed
parent-
}
NetworkListWork
{
delete m_dirIterator;
delete m_mainItemInfo;
if (m_parent != 0)
{
delete m_parent;
}
}
Arto Jalkanen (ajalkane) wrote : | # |
I'm a bit dumbfounded that this kind of Qt metaprogramming would be necessary for solving a problem as basic as what we have here. At the minimum I think this code would need to be commented, because if anyone stumbles upon this code in the future it'd be a big WTF moment without clarifying comments. Comments should explain what it fixes, why it had to be done this way.
Renato Araujo Oliveira Filho (renatofilho) wrote : | # |
> Another and easy approach would be if the class NetworkListWorker receive
> again a const DirItemInfo *parent, but sets m_parent as its own instance using
> the operator '=', in this case it never gets deleted outside the worker
> thread.
>
> Suppose the code changed:
>
>
> NetworkListWork
> DirItemInfo * mainItemInfo,
> const DirItemInfo *parent) :
> DirListWorker(
> dirIterator-
> dirIterator-
> : false),
> m_dirIterator(
> m_mainItemInfo(
> m_parent(0)
> {
> if (parent != 0)
> {
> m_parent = new UrlItemInfo(); // UrlItemInfo is for remote
> *m_parent = *parent; // not sure it works, needs be reviewed
> }
> mLoaderType = NetworkLoader;
>
> // this would not be necessary and can be removed
> parent-
> }
>
>
> NetworkListWork
> {
> delete m_dirIterator;
> delete m_mainItemInfo;
> if (m_parent != 0)
> {
> delete m_parent;
> }
> }
With that code the operation will be executed even after the "parent" object be destroyed what we do not want. It is waste of resource.
Renato Araujo Oliveira Filho (renatofilho) wrote : | # |
> I'm a bit dumbfounded that this kind of Qt metaprogramming would be necessary
> for solving a problem as basic as what we have here. At the minimum I think
> this code would need to be commented, because if anyone stumbles upon this
> code in the future it'd be a big WTF moment without clarifying comments.
> Comments should explain what it fixes, why it had to be done this way.
The Qt metaprogramming make things easy here. Avoid big changes. Without that we will need to keep a list of child objects on the parent and invalidate all the children when parent get destroyed.
Instead of that I am keep all the changes on child object and keeping track of parent life. Any suggestion are welcome.
Carlos Jose Mazieri (carlos-mazieri) wrote : | # |
I would remove all the Qt metaprogramming as it is not necessary,
Only a small change in the NetworkListWorker as exposed above should solve the problem, NetworkListWorker class can create its own instance by doing a copy.
Carlos Jose Mazieri (carlos-mazieri) wrote : | # |
Renato,
Can you please test that proposal of that NetworkListWorker creating its own instance of the parent item?
There is a proposal https:/
Also renamed "parent" to "parentItemInfo".
Renato Araujo Oliveira Filho (renatofilho) wrote : | # |
> Renato,
>
> Can you please test that proposal of that NetworkListWorker creating its own
> instance of the parent item?
>
> There is a proposal https:/
> filemanager-
>
> Also renamed "parent" to "parentItemInfo".
Thanks, Carlos will be a pleasure test that.
But as mentioned before if the parent get destroyed the operation will not be canceled and will be executed until the end. (In my opinion this is a wast of resources, but if you are ok with that)
Based on that information I do not think you need to check for (m_parentItemInfo != 0) in line 61. Since m_parentItemInfo will never be null.
Carlos Jose Mazieri (carlos-mazieri) wrote : | # |
Renato,
The check in line 61 is necessary.
The 'parentItemInfo' keeps the information from the parent, which can be: directory/
When the 'mainItemInfo' is root like "smb://" there is no 'parentItemInfo', it can be null.
Another reason it might be used in my regression tests (I am not sure if it is), you can use if you want its in "src/plugin/
Renato Araujo Oliveira Filho (renatofilho) wrote : | # |
Closing this MR
Replaced by: https:/
Jenkins Bot (ubuntu-core-apps-jenkins-bot) wrote : | # |
FAILED: Continuous integration, rev:588
https:/
Executed test runs:
None: https:/
Click here to trigger a rebuild:
https:/
Unmerged revisions
- 588. By Renato Araujo Oliveira Filho
-
Remove unused code.
Check for parent before use it. - 587. By Renato Araujo Oliveira Filho
-
Fix grammar.
- 586. By Renato Araujo Oliveira Filho
-
Remove extra debug messages.
- 585. By Renato Araujo Oliveira Filho
-
Fixed memory leak;
Make sure that works are deleted.
- 584. By Renato Araujo Oliveira Filho
-
Avoid crash when aborting a network operation.
Clear parent pointer on worker if parent get destroyed.
Preview Diff
1 | === modified file 'src/plugin/folderlistmodel/diriteminfo.cpp' | |||
2 | --- src/plugin/folderlistmodel/diriteminfo.cpp 2015-12-12 12:30:47 +0000 | |||
3 | +++ src/plugin/folderlistmodel/diriteminfo.cpp 2017-01-25 16:54:04 +0000 | |||
4 | @@ -23,6 +23,8 @@ | |||
5 | 23 | #include "locationurl.h" | 23 | #include "locationurl.h" |
6 | 24 | #include <sys/types.h> | 24 | #include <sys/types.h> |
7 | 25 | #include <sys/stat.h> | 25 | #include <sys/stat.h> |
8 | 26 | #include <QMetaMethod> | ||
9 | 27 | #include <QDebug> | ||
10 | 26 | 28 | ||
11 | 27 | 29 | ||
12 | 28 | QMimeDatabase DirItemInfoPrivate::mimeDatabase; | 30 | QMimeDatabase DirItemInfoPrivate::mimeDatabase; |
13 | @@ -151,6 +153,13 @@ | |||
14 | 151 | 153 | ||
15 | 152 | DirItemInfo::~DirItemInfo() | 154 | DirItemInfo::~DirItemInfo() |
16 | 153 | { | 155 | { |
17 | 156 | Q_FOREACH(const DestructionSlot &slot, d_ptr->_destructionSlots) { | ||
18 | 157 | if (slot.first) { | ||
19 | 158 | const QMetaObject *mObject = slot.first->metaObject(); | ||
20 | 159 | QMetaMethod method = mObject->method(slot.second); | ||
21 | 160 | method.invoke(slot.first.data(), Qt::DirectConnection); | ||
22 | 161 | } | ||
23 | 162 | } | ||
24 | 154 | } | 163 | } |
25 | 155 | 164 | ||
26 | 156 | 165 | ||
27 | @@ -602,3 +611,13 @@ | |||
28 | 602 | { | 611 | { |
29 | 603 | d_ptr->_isNetworkShare = true; | 612 | d_ptr->_isNetworkShare = true; |
30 | 604 | } | 613 | } |
31 | 614 | |||
32 | 615 | void DirItemInfo::connectDestructionSignal(QObject *obj, const QByteArray &slot) | ||
33 | 616 | { | ||
34 | 617 | int index = obj->metaObject()->indexOfSlot(slot.constData()+1); | ||
35 | 618 | if (index == -1) { | ||
36 | 619 | qWarning() << "Object" << obj << "does not have slot named:" << slot; | ||
37 | 620 | } else { | ||
38 | 621 | d_ptr->_destructionSlots.append(qMakePair(QPointer<QObject>(obj), index)); | ||
39 | 622 | } | ||
40 | 623 | } | ||
41 | 605 | 624 | ||
42 | === modified file 'src/plugin/folderlistmodel/diriteminfo.h' | |||
43 | --- src/plugin/folderlistmodel/diriteminfo.h 2015-12-12 12:30:47 +0000 | |||
44 | +++ src/plugin/folderlistmodel/diriteminfo.h 2017-01-25 16:54:04 +0000 | |||
45 | @@ -30,6 +30,8 @@ | |||
46 | 30 | #include <QDir> | 30 | #include <QDir> |
47 | 31 | #include <QMimeType> | 31 | #include <QMimeType> |
48 | 32 | #include <QMimeDatabase> | 32 | #include <QMimeDatabase> |
49 | 33 | #include <QPair> | ||
50 | 34 | #include <QPointer> | ||
51 | 33 | 35 | ||
52 | 34 | class DirItemInfoPrivate; | 36 | class DirItemInfoPrivate; |
53 | 35 | 37 | ||
54 | @@ -114,6 +116,7 @@ | |||
55 | 114 | void fillFromStatBuf(const struct stat& statBuffer); | 116 | void fillFromStatBuf(const struct stat& statBuffer); |
56 | 115 | void setAsHost(); | 117 | void setAsHost(); |
57 | 116 | void setAsShare(); | 118 | void setAsShare(); |
58 | 119 | void connectDestructionSignal(QObject *obj, const QByteArray &slot); | ||
59 | 117 | 120 | ||
60 | 118 | public: | 121 | public: |
61 | 119 | static QString removeExtraSlashes(const QString &url, int firstSlashIndex = -1); | 122 | static QString removeExtraSlashes(const QString &url, int firstSlashIndex = -1); |
62 | @@ -134,6 +137,7 @@ | |||
63 | 134 | }; | 137 | }; |
64 | 135 | 138 | ||
65 | 136 | typedef QVector<DirItemInfo> DirItemInfoList; | 139 | typedef QVector<DirItemInfo> DirItemInfoList; |
66 | 140 | typedef QPair<QPointer<QObject>, int> DestructionSlot; | ||
67 | 137 | 141 | ||
68 | 138 | Q_DECLARE_SHARED(DirItemInfo) | 142 | Q_DECLARE_SHARED(DirItemInfo) |
69 | 139 | Q_DECLARE_METATYPE(DirItemInfo) | 143 | Q_DECLARE_METATYPE(DirItemInfo) |
70 | @@ -180,6 +184,8 @@ | |||
71 | 180 | QString _normalizedPath; | 184 | QString _normalizedPath; |
72 | 181 | QString _authenticationPath; | 185 | QString _authenticationPath; |
73 | 182 | 186 | ||
74 | 187 | QVector<DestructionSlot> _destructionSlots; | ||
75 | 188 | |||
76 | 183 | static QMimeDatabase mimeDatabase; | 189 | static QMimeDatabase mimeDatabase; |
77 | 184 | }; | 190 | }; |
78 | 185 | 191 | ||
79 | 186 | 192 | ||
80 | === modified file 'src/plugin/folderlistmodel/iorequestworker.cpp' | |||
81 | --- src/plugin/folderlistmodel/iorequestworker.cpp 2013-10-12 19:25:17 +0000 | |||
82 | +++ src/plugin/folderlistmodel/iorequestworker.cpp 2017-01-25 16:54:04 +0000 | |||
83 | @@ -36,6 +36,8 @@ | |||
84 | 36 | #include <QDateTime> | 36 | #include <QDateTime> |
85 | 37 | #include <QDebug> | 37 | #include <QDebug> |
86 | 38 | 38 | ||
87 | 39 | #define ORIGNAL_THREAD "ORIGINAL_THREAD" | ||
88 | 40 | |||
89 | 39 | /*! | 41 | /*! |
90 | 40 | Lives on an IOWorkerThread. | 42 | Lives on an IOWorkerThread. |
91 | 41 | 43 | ||
92 | @@ -55,6 +57,7 @@ | |||
93 | 55 | << Q_FUNC_INFO; | 57 | << Q_FUNC_INFO; |
94 | 56 | #endif | 58 | #endif |
95 | 57 | 59 | ||
96 | 60 | request->setProperty(ORIGNAL_THREAD, QVariant::fromValue<QThread*>(request->thread())); | ||
97 | 58 | request->moveToThread(this); | 61 | request->moveToThread(this); |
98 | 59 | 62 | ||
99 | 60 | // TODO: queue requests so we run the most important one first | 63 | // TODO: queue requests so we run the most important one first |
100 | @@ -82,7 +85,11 @@ | |||
101 | 82 | lock.unlock(); | 85 | lock.unlock(); |
102 | 83 | 86 | ||
103 | 84 | request->run(); | 87 | request->run(); |
104 | 88 | |||
105 | 89 | // transfer back to main loop to make sure that will be destoyed by deleteLater | ||
106 | 90 | request->moveToThread(request->property(ORIGNAL_THREAD).value<QThread*>()); | ||
107 | 85 | request->deleteLater(); | 91 | request->deleteLater(); |
108 | 92 | |||
109 | 86 | lock.relock(); | 93 | lock.relock(); |
110 | 87 | } | 94 | } |
111 | 88 | } | 95 | } |
112 | 89 | 96 | ||
113 | === modified file 'src/plugin/folderlistmodel/location.cpp' | |||
114 | --- src/plugin/folderlistmodel/location.cpp 2015-12-08 16:55:41 +0000 | |||
115 | +++ src/plugin/folderlistmodel/location.cpp 2017-01-25 16:54:04 +0000 | |||
116 | @@ -326,6 +326,5 @@ | |||
117 | 326 | { | 326 | { |
118 | 327 | m_info = new DirItemInfo(); | 327 | m_info = new DirItemInfo(); |
119 | 328 | } | 328 | } |
120 | 329 | refreshInfo(); //update information | ||
121 | 330 | return m_info; | 329 | return m_info; |
122 | 331 | } | 330 | } |
123 | 332 | 331 | ||
124 | === modified file 'src/plugin/folderlistmodel/location.h' | |||
125 | --- src/plugin/folderlistmodel/location.h 2015-12-12 13:59:49 +0000 | |||
126 | +++ src/plugin/folderlistmodel/location.h 2017-01-25 16:54:04 +0000 | |||
127 | @@ -91,7 +91,6 @@ | |||
128 | 91 | virtual void setAuthentication(const QString& user, | 91 | virtual void setAuthentication(const QString& user, |
129 | 92 | const QString& password); | 92 | const QString& password); |
130 | 93 | 93 | ||
131 | 94 | |||
132 | 95 | public: //pure functions | 94 | public: //pure functions |
133 | 96 | /*! | 95 | /*! |
134 | 97 | * \brief newItemInfo() returns a Location suitable DirItemInfo object | 96 | * \brief newItemInfo() returns a Location suitable DirItemInfo object |
135 | 98 | 97 | ||
136 | === modified file 'src/plugin/folderlistmodel/networklistworker.cpp' | |||
137 | --- src/plugin/folderlistmodel/networklistworker.cpp 2015-12-12 14:32:18 +0000 | |||
138 | +++ src/plugin/folderlistmodel/networklistworker.cpp 2017-01-25 16:54:04 +0000 | |||
139 | @@ -23,8 +23,10 @@ | |||
140 | 23 | #include "locationitemdiriterator.h" | 23 | #include "locationitemdiriterator.h" |
141 | 24 | #include "locationurl.h" | 24 | #include "locationurl.h" |
142 | 25 | 25 | ||
143 | 26 | #include <QDebug> | ||
144 | 27 | |||
145 | 26 | NetworkListWorker::NetworkListWorker(LocationItemDirIterator * dirIterator, | 28 | NetworkListWorker::NetworkListWorker(LocationItemDirIterator * dirIterator, |
147 | 27 | DirItemInfo * mainItemInfo, const DirItemInfo *parent) : | 29 | DirItemInfo * mainItemInfo, DirItemInfo *parent) : |
148 | 28 | DirListWorker(dirIterator->path(), | 30 | DirListWorker(dirIterator->path(), |
149 | 29 | dirIterator->filters(), | 31 | dirIterator->filters(), |
150 | 30 | dirIterator->flags() == QDirIterator::Subdirectories ? true : false), | 32 | dirIterator->flags() == QDirIterator::Subdirectories ? true : false), |
151 | @@ -33,6 +35,8 @@ | |||
152 | 33 | m_parent(parent) | 35 | m_parent(parent) |
153 | 34 | { | 36 | { |
154 | 35 | mLoaderType = NetworkLoader; | 37 | mLoaderType = NetworkLoader; |
155 | 38 | if (parent) | ||
156 | 39 | parent->connectDestructionSignal(this, SLOT(onParentDestroyed())); | ||
157 | 36 | } | 40 | } |
158 | 37 | 41 | ||
159 | 38 | 42 | ||
160 | @@ -42,10 +46,15 @@ | |||
161 | 42 | delete m_mainItemInfo; | 46 | delete m_mainItemInfo; |
162 | 43 | } | 47 | } |
163 | 44 | 48 | ||
164 | 49 | void NetworkListWorker::onParentDestroyed() | ||
165 | 50 | { | ||
166 | 51 | m_parent = 0; | ||
167 | 52 | } | ||
168 | 45 | 53 | ||
169 | 46 | DirItemInfoList NetworkListWorker::getNetworkContent() | 54 | DirItemInfoList NetworkListWorker::getNetworkContent() |
170 | 47 | { | 55 | { |
171 | 48 | DirItemInfoList netContent; | 56 | DirItemInfoList netContent; |
172 | 57 | |||
173 | 49 | m_dirIterator->load(); | 58 | m_dirIterator->load(); |
174 | 50 | bool is_parent_of_smb_url = m_parent != 0 && m_parent->urlPath().startsWith(LocationUrl::SmbURL); | 59 | bool is_parent_of_smb_url = m_parent != 0 && m_parent->urlPath().startsWith(LocationUrl::SmbURL); |
175 | 51 | while (m_dirIterator->hasNext()) | 60 | while (m_dirIterator->hasNext()) |
176 | 52 | 61 | ||
177 | === modified file 'src/plugin/folderlistmodel/networklistworker.h' | |||
178 | --- src/plugin/folderlistmodel/networklistworker.h 2015-12-12 14:32:18 +0000 | |||
179 | +++ src/plugin/folderlistmodel/networklistworker.h 2017-01-25 16:54:04 +0000 | |||
180 | @@ -40,11 +40,15 @@ | |||
181 | 40 | public: | 40 | public: |
182 | 41 | NetworkListWorker(LocationItemDirIterator * dirIterator, | 41 | NetworkListWorker(LocationItemDirIterator * dirIterator, |
183 | 42 | DirItemInfo * mainItemInfo, | 42 | DirItemInfo * mainItemInfo, |
185 | 43 | const DirItemInfo * parent = 0); | 43 | DirItemInfo * parent = 0); |
186 | 44 | ~NetworkListWorker(); | 44 | ~NetworkListWorker(); |
187 | 45 | public slots: | ||
188 | 46 | void onParentDestroyed(); | ||
189 | 47 | |||
190 | 45 | protected: | 48 | protected: |
191 | 46 | virtual DirItemInfoList getNetworkContent(); | 49 | virtual DirItemInfoList getNetworkContent(); |
192 | 47 | void setSmbItemAttributes(); | 50 | void setSmbItemAttributes(); |
193 | 51 | |||
194 | 48 | protected: | 52 | protected: |
195 | 49 | LocationItemDirIterator * m_dirIterator; | 53 | LocationItemDirIterator * m_dirIterator; |
196 | 50 | DirItemInfo * m_mainItemInfo; | 54 | DirItemInfo * m_mainItemInfo; |
Can you explain how this change addresses the issue?