J'ai rencontré une situation difficile à déboguer dans un de mes vrais projets où j'accédais accidentellement à une référence à une variable locale dans un lambda qui avait été déplacé. L'accès se faisait à partir d'un autre thread, mais le lambda déplacé était maintenu en vie jusqu'à ce que le deuxième thread soit terminé.Attraper et déboguer une utilisation invalide de la référence à une variable locale à l'intérieur de lambda
Le bug ne s'est produit qu'avec des optimisations désactivées et a été causé par un refactoring négligent.
J'ai créé un exemple minimal (available here on wandbox) qui reproduit la question:
struct state
{
int x = 100;
};
template <typename TF>
void eat1(TF&& f)
{
// Call the lambda.
f();
// Simulate waiting for the second thread
// to finish.
std::this_thread::sleep_for(1000ms);
}
template <typename TF>
void eat0(TF&& f)
{
// Move the lambda to some other handler.
eat1(std::forward<TF>(f));
}
void use_state(state& s)
{
// Will print `100`.
std::cout << s.x << "\n";
// Separate thread. Note that `s` is captured by
// reference.
std::thread t{[&s]
{
// Simulate computation delay.
std::this_thread::sleep_for(500ms);
// Will print garbage.
std::cout << s.x << "\n";
}};
t.detach();
}
int main()
{
eat0([]
{
// Local lambda variable that will be accessed
// after the lambda is moved.
state s;
// Function that takes `s` by reference and
// accesses it in a separate thread after the
// lambda is moved.
use_state(s);
});
}
Étonnamment, aucun des assainissants et drapeaux d'avertissement a réussi à aider.
J'ai essayé les combinaisons suivantes de compilateurs et assainissants, avec
-Wall -Wextra -Wpedantic -g -O0
drapeaux toujours activés:
Compilateurs: g ++ 6.1.1 sur Arch Linux x64 ; clang ++ 3.8.0 sur Arch Linux x64; g ++ 5.3.1 sur Fedora x64; clang ++ 3.7.0 sur Fedora x64.
Désinfectants:
-fsanitize=address
;-fsanitize=undefined
,-fsanitize=thread
.
Aucune des combinaisons n'a produit de diagnostic utile. Je me attendais soit AddressSanitizer pour me dire que j'accédait à une référence ballants ou UndefinedSanitizer pour attraper UB lors de l'accès, ou ThreadSanitizer pour me dire un thread séparé accédait à un emplacement de mémoire non valide.
Existe-t-il un moyen fiable de diagnostiquer ce problème? Est-ce que je devrais publier cet exemple sur l'un des suiveurs de bogues de désinfectants en tant que demande/défaut de fonctionnalité?
C++ est compliqué. Il n'y a rien de mal à capturer par référence. Cela fait partie de la langue. Si le timing des threads est différent, ceci est bien défini.Je ne vois pas comment le compilateur peut déduire le timing des threads d'exécution. Vous ne pouvez pas compter sur le compilateur tout attraper. –
Eh bien, j'ai essayé l'analyse statique de MSVS qui utilise les lignes directrices de base de CPP et il ne l'a pas non plus détecté. Soit cette vérification n'a pas encore été implémentée, soit il n'existe pas encore de règle couvrant ce cas. Je ne sais pas si vous voulez soulever un problème pour voir si cela pourrait être diagnostiqué. [link] (https://github.com/isocpp/CppCoreGuidelines) – NathanOliver