forked from OSchip/llvm-project
258 lines
7.4 KiB
ReStructuredText
258 lines
7.4 KiB
ReStructuredText
.. index:: Loop Convert Transform
|
|
|
|
======================
|
|
Loop Convert Transform
|
|
======================
|
|
|
|
The Loop Convert Transform is a transformation to convert ``for(...; ...;
|
|
...)`` loops to use the new range-based loops in C++11. The transform is enabled
|
|
with the :option:`-loop-convert` option of :program:`cpp11-migrate`.
|
|
|
|
Three kinds of loops can be converted:
|
|
|
|
- Loops over statically allocated arrays
|
|
- Loops over containers, using iterators
|
|
- Loops over array-like containers, using ``operator[]`` and ``at()``
|
|
|
|
Risk
|
|
====
|
|
|
|
Risky
|
|
-----
|
|
|
|
In loops where the container expression is more complex than just a
|
|
reference to a declared expression (a variable, function, enum, etc.),
|
|
and some part of it appears elsewhere in the loop, we lower our confidence
|
|
in the transformation due to the increased risk of changing semantics.
|
|
Transformations for these loops are marked as `risky`, and thus will only
|
|
be converted if the acceptable risk level is set to ``-risk=risky``.
|
|
|
|
.. code-block:: c++
|
|
|
|
int arr[10][20];
|
|
int l = 5;
|
|
|
|
for (int j = 0; j < 20; ++j)
|
|
int k = arr[l][j] + l; // using l outside arr[l] is considered risky
|
|
|
|
for (int i = 0; i < obj.getVector().size(); ++i)
|
|
obj.foo(10); // using 'obj' is considered risky
|
|
|
|
See
|
|
:ref:`Range-based loops evaluate end() only once<IncorrectRiskyTransformation>`
|
|
for an example of an incorrect transformation when the maximum acceptable risk
|
|
level is set to `risky`.
|
|
|
|
Reasonable (Default)
|
|
--------------------
|
|
|
|
If a loop calls ``.end()`` or ``.size()`` after each iteration, the
|
|
transformation for that loop is marked as `reasonable`, and thus will
|
|
be converted if the acceptable risk level is set to ``-risk=reasonable``
|
|
(default) or higher.
|
|
|
|
.. code-block:: c++
|
|
|
|
// using size() is considered reasonable
|
|
for (int i = 0; i < container.size(); ++i)
|
|
cout << container[i];
|
|
|
|
Safe
|
|
----
|
|
|
|
Any other loops that do not match the above criteria to be marked as
|
|
`risky` or `reasonable` are marked `safe`, and thus will be converted
|
|
if the acceptable risk level is set to ``-risk=safe`` or higher.
|
|
|
|
.. code-block:: c++
|
|
|
|
int arr[] = {1,2,3};
|
|
|
|
for (int i = 0; i < 3; ++i)
|
|
cout << arr[i];
|
|
|
|
Example
|
|
=======
|
|
|
|
Original:
|
|
|
|
.. code-block:: c++
|
|
|
|
const int N = 5;
|
|
int arr[] = {1,2,3,4,5};
|
|
vector<int> v;
|
|
v.push_back(1);
|
|
v.push_back(2);
|
|
v.push_back(3);
|
|
|
|
// safe transform
|
|
for (int i = 0; i < N; ++i)
|
|
cout << arr[i];
|
|
|
|
// reasonable transform
|
|
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it)
|
|
cout << *it;*
|
|
|
|
// reasonable transform
|
|
for (int i = 0; i < v.size(); ++i)
|
|
cout << v[i];
|
|
|
|
After transformation with risk level set to ``-risk=reasonable`` (default):
|
|
|
|
.. code-block:: c++
|
|
|
|
const int N = 5;
|
|
int arr[] = {1,2,3,4,5};
|
|
vector<int> v;
|
|
v.push_back(1);
|
|
v.push_back(2);
|
|
v.push_back(3);
|
|
|
|
// safe transform
|
|
for (auto & elem : arr)
|
|
cout << elem;
|
|
|
|
// reasonable transform
|
|
for (auto & elem : v)
|
|
cout << elem;
|
|
|
|
// reasonable transform
|
|
for (auto & elem : v)
|
|
cout << elem;
|
|
|
|
Limitations
|
|
===========
|
|
|
|
There are certain situations where the tool may erroneously perform
|
|
transformations that remove information and change semantics. Users of the tool
|
|
should be aware of the behaviour and limitations of the transform outlined by
|
|
the cases below.
|
|
|
|
Comments inside loop headers
|
|
----------------------------
|
|
|
|
Comments inside the original loop header are ignored and deleted when
|
|
transformed.
|
|
|
|
.. code-block:: c++
|
|
|
|
for (int i = 0; i < N; /* This will be deleted */ ++i) { }
|
|
|
|
Range-based loops evaluate end() only once
|
|
------------------------------------------
|
|
|
|
The C++11 range-based for loop calls ``.end()`` only once during the
|
|
initialization of the loop. If in the original loop ``.end()`` is called after
|
|
each iteration the semantics of the transformed loop may differ.
|
|
|
|
.. code-block:: c++
|
|
|
|
// The following is semantically equivalent to the C++11 range-based for loop,
|
|
// therefore the semantics of the header will not change.
|
|
for (iterator it = container.begin(), e = container.end(); it != e; ++it) { }
|
|
|
|
// Instead of calling .end() after each iteration, this loop will be
|
|
// transformed to call .end() only once during the initialization of the loop,
|
|
// which may affect semantics.
|
|
for (iterator it = container.begin(); it != container.end(); ++it) { }
|
|
|
|
.. _IncorrectRiskyTransformation:
|
|
|
|
As explained above, calling member functions of the container in the body
|
|
of the loop is considered `risky`. If the called member function modifies the
|
|
container the semantics of the converted loop will differ due to ``.end()``
|
|
being called only once.
|
|
|
|
.. code-block:: c++
|
|
|
|
bool flag = false;
|
|
for (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it) {
|
|
// Add a copy of the first element to the end of the vector.
|
|
if (!flag) {
|
|
// This line makes this transformation 'risky'.
|
|
vec.push_back(*it);
|
|
flag = true;
|
|
}
|
|
cout << *it;
|
|
}
|
|
|
|
The original code above prints out the contents of the container including the
|
|
newly added element while the converted loop, shown below, will only print the
|
|
original contents and not the newly added element.
|
|
|
|
.. code-block:: c++
|
|
|
|
bool flag = false;
|
|
for (auto & elem : vec) {
|
|
// Add a copy of the first element to the end of the vector.
|
|
if (!flag) {
|
|
// This line makes this transformation 'risky'
|
|
vec.push_back(elem);
|
|
flag = true;
|
|
}
|
|
cout << elem;
|
|
}
|
|
|
|
Semantics will also be affected if ``.end()`` has side effects. For example, in
|
|
the case where calls to ``.end()`` are logged the semantics will change in the
|
|
transformed loop if ``.end()`` was originally called after each iteration.
|
|
|
|
.. code-block:: c++
|
|
|
|
iterator end() {
|
|
num_of_end_calls++;
|
|
return container.end();
|
|
}
|
|
|
|
Overloaded operator->() with side effects
|
|
-----------------------------------------
|
|
|
|
Similarly, if ``operator->()`` was overloaded to have side effects, such as
|
|
logging, the semantics will change. If the iterator's ``operator->()`` was used
|
|
in the original loop it will be replaced with ``<container element>.<member>``
|
|
instead due to the implicit dereference as part of the range-based for loop.
|
|
Therefore any side effect of the overloaded ``operator->()`` will no longer be
|
|
performed.
|
|
|
|
.. code-block:: c++
|
|
|
|
for (iterator it = c.begin(); it != c.end(); ++it) {
|
|
it->func(); // Using operator->()
|
|
}
|
|
// Will be transformed to:
|
|
for (auto & elem : c) {
|
|
elem.func(); // No longer using operator->()
|
|
}
|
|
|
|
Pointers and references to containers
|
|
-------------------------------------
|
|
|
|
While most of the transform's risk analysis is dedicated to determining whether
|
|
the iterator or container was modified within the loop, it is possible to
|
|
circumvent the analysis by accessing and modifying the container through a
|
|
pointer or reference.
|
|
|
|
If the container were directly used instead of using the pointer or reference
|
|
the following transformation would have only been applied at the ``-risk=risky``
|
|
level since calling a member function of the container is considered `risky`.
|
|
The transform cannot identify expressions associated with the container that are
|
|
different than the one used in the loop header, therefore the transformation
|
|
below ends up being performed at the ``-risk=safe`` level.
|
|
|
|
.. code-block:: c++
|
|
|
|
vector<int> vec;
|
|
|
|
vector<int> *ptr = &vec;
|
|
vector<int> &ref = vec;
|
|
|
|
for (vector<int>::iterator it = vec.begin(), e = vec.end(); it != e; ++it) {
|
|
if (!flag) {
|
|
// Accessing and modifying the container is considered risky, but the risk
|
|
// level is not raised here.
|
|
ptr->push_back(*it);
|
|
ref.push_back(*it);
|
|
flag = true;
|
|
}
|
|
}
|