Pytanie Statyczna analiza "naruszeń" "noexcept" w C ++


Próbuję napisać kod bezpieczny wyjątek. Uważam, że za pomocą specyfikatora noexcept C ++ 11 cel ten jest o wiele bardziej osiągalny.

Ogólną ideą jest oczywiście to, że funkcja powinna być oznaczona jako "noexcept" tylko wtedy, gdy wszystkie funkcje, które wywołuje, są również oznaczone jako "noexcept".

Problem polega na tym, że w dużej bazie kodu, w której łatki różnych osób są często łączone, trudno jest zapewnić zachowanie tej spójności.

Chciałbym więc móc przeprowadzić analizę statyczną, która mogłaby wyświetlić wszystkie miejsca, w których funkcja oznaczona jako "nothrow" wywołuje funkcję, która nie jest oznaczona jako "nothrow".

O ile widzę na stronie man, GCC nie może mi tu pomóc. Czy są jakieś samodzielne narzędzia, które mogą mi pomóc? A może jakieś inne kompilatory?


12
2018-02-01 17:53


pochodzenie


To zawsze jest fajny pomysł, ale może się nie udać w praktyce, ponieważ zwiększa się złożoność funkcji braku rzucania. Przykład, który widziałem gdzie indziej: double safe_sqrt(double x) { if (x < 0) throw "no"; return sqrt(x); } double abs_sqrt(double x) noexcept { return safe_sqrt(abs(x)); } Funkcja noexcept wywołuje funkcję rzutu, ale jest to bezpieczne, ponieważ nie można dotrzeć do ścieżki rzutu. Jednak analiza statyczna nie jest już tak prosta, jak "czy nazywam funkcjami noexcept". Nadal wykonalne, jasne, ale o wiele trudniejsze. I są rozwiązania, ale wątpię, że są one proste w każdym przypadku (ten jest). - GManNickG
Tak, jest to jednak problem, w takich przypadkach można zaakceptować "fałszywy alarm" z analizatora, lub po prostu nie oznaczać abs_sqrt jako "noexcept". Wciąż uważam, że takie narzędzie zapewniłoby wielką wartość. - Kristian Spangsege
btw możesz przeczytać: akrzemi1.wordpress.com/2011/06/10/using-noexcept (ktoś wysłał to do mojego odczytywania Q) - NoSenseEtAl
Rzuć okiem na klang ... może już go nie wdrożyć, ale łatwo jest rozszerzyć analizator - David Rodríguez - dribeas
@NoSenseEtAl: Według andrzejowskiego bloga "Statyczne sprawdzanie gwarancji braku rzutu, z ulepszonym narzędziem do lokalnego wyłączania czeku". prawdopodobnie będzie częścią następnej wersji C ++. Miły! - Kristian Spangsege


Odpowiedzi:


Z pewnością wydaje się to możliwe, jeśli unikasz wskaźnika do funkcji (i uznasz je za niebezpieczne) za pomocą ABT programu. AST Klang jest (pomimo swojej nazwy) takim ABT: zobaczysz zarówno deklaracje, jak i definicje funkcji. Wykonując swoją pracę po jednej definicji, będziesz mieć już dobrą linię bazową.

Z drugiej strony zastanawiam się, czy tak jest praktyczny. Problem polega na tym, że każda funkcja dokonująca alokacji pamięci jest (dobrowolnie) oznaczona jako potencjalnie rzucająca (ponieważ new nigdy nie zwraca null, ale rzuca bad_alloc zamiast). Dlatego twoje noexcept będzie w większości przypadków ograniczone do kilku funkcji.

Oczywiście wszystkie warunki dynamiczne, takie jak @GManNickG, są naświetlone, na przykład:

void foo(boost::optional<T> const t&) {
    if (not t) { return; }

    t->bar();
}

Choćby T::bar jest noexcept, dereferencje optional<T> może rzucić (jeśli nie ma nic). Oczywiście ignoruje to fakt, że już to wykluczyliśmy (tutaj).

Bez jasności warunki włączone, gdy funkcja może działać throw, static Analiza może okazać się ... bezużyteczna. Idiomy językowe są projektowane z myślą o wyjątkach.


Uwaga: jako dygresja, opcjonalna klasa mogłaby zostać przepisana, aby nie narażać dereferencji, a tym samym być noexcept (jeśli są to wywołania zwrotne):

template <typename T>
class maybe {
public:

    template <typename OnNone, typename OnJust>
    void act(OnNone&& n, OnJust&& j) noexcept(noexcept(n()) and 
                                              noexcept(j(std::declval<T&>())))
    {
        if (not _assigned) { n(); return; }
        j(*reinterpret_cast<T*>(&_storage));
    }

private:
    std::aligned_storage<sizeof(T), alignof(T)>::type _storage;
    bool _assigned;
};

// No idea if this way of expressing the noexcept dependency is actually correct.

6
2018-02-01 18:38



Dynamiczne alokacje mogą zawieść, to tylko fakt. Z tego powodu (i innych) staram się unikać dynamicznych alokacji. W kodzie, nad którym pracuję, alokacja dynamiczna jest głównym powodem, dla którego wszystkie funkcje nie są "nothrow". - Kristian Spangsege
@KristianSpangsege: Niestety, to może porażka, ale przynajmniej na Linuksie, nie będzie z powodu nadmiernej alokacji i zamiast tego dostaniesz segfault. Więc płacisz noexcept cena za to, ale twój program ulegnie awarii przed bad_alloc jest rzucony: sytuacja jest przegrana / przegrana. - Matthieu M.
Jestem pewien, że alokacja dynamiczna może się nie udać również w Linuksie, jeśli pamięć wirtualna jest ograniczona specjalnie do twojego procesu ulimit. - Kristian Spangsege
W systemie OSX nie ma jednak możliwości ograniczenia pamięci wirtualnej dla procesu, więc na tej platformie może się zdarzyć, że alokacja dynamiczna nie powiedzie się. - Kristian Spangsege
@KristianSpangsege: może ... Na pewno nie znam tajników; jednak bad_alloc nadal jest zepsuta obietnica, ponieważ obietnica powinna być zawsze w posiadaniu! I ta niewiarygodna obietnica, niestety, sprawi noexcept analiza prawie bezużyteczna. Chyba że możliwe jest przeciążenie globalne new i podzielniki z noexcept wersje, ale nawet wtedy nie jestem pewien, czy implementacja biblioteki standardowej poprawnie propaguje atrybut. - Matthieu M.