Poznanie SAL
Język opisu kodu źródłowego Microsoft (SAL) udostępnia zestaw adnotacji, które służą do opisywania, jak funkcja wykorzystuje jego parametry, założeń, które to sprawia, że o nich i gwarancje, że to sprawia, że po jego zakończeniu.Adnotacje są zdefiniowane w pliku nagłówka <sal.h>.Analizy kodu Visual Studio c++ używa SAL adnotacje do modyfikowania ich analizy funkcji.Aby uzyskać więcej informacji na temat SAL 2.0 dla Windows driver development, zobacz SAL 2.0 adnotacje dla sterowników systemu Windows.
Natywnie C i C++ zawierają tylko ograniczone możliwości dla deweloperów konsekwentnie wyrazić zamiar i atak na niezmienność klucza.Za pomocą adnotacji SAL, możesz opisać swoje funkcje w sposób bardziej szczegółowy, aby deweloperzy, którzy zużywają je można lepiej zrozumieć, jak z nich korzystać.
Co to jest SAL i dlaczego warto używać go?
Krótko mówiąc, SAL jest niedrogi sposób, aby umożliwić kompilatora, sprawdź swój kod.
SAL sprawia, że kod bardziej wartościowe
SAL ułatwiają wprowadzanie swój projekt kodu bardziej zrozumiałe dla ludzi i narzędzia analizy kodu.Ten przykład, który pokazuje funkcji środowiska wykonawczego języka C memcpy:
void * memcpy(
void *dest,
const void *src,
size_t count
);
Można sprawdzić, czy ta funkcja?Gdy funkcja jest zaimplementowana lub o nazwie, niektórych właściwości musi być utrzymywany zapewnienie poprawności programu.Rzut deklarację taką jak w przykładzie, nie wiesz, czym one są.Bez adnotacje SAL będą musiały opierać się na dokumentacji lub komentarzy do kodu.W tym miejscu jest w dokumentacji MSDN dla memcpy mówi:
"Kopie zliczanie bajtów src do dest.Jeśli źródłowy i docelowy nachodzą na siebie, zachowanie memcpy jest niezdefiniowany.Do obsługi pokrywających się obszarów, należy użyć memmove.Uwaga dotycząca zabezpieczeń: upewnij się, że bufor docelowy jest taki sam lub większy rozmiar niż bufor źródłowy.Aby uzyskać więcej informacji zobacz temat unikania przekroczeniem buforu."
Dokumentacja zawiera kilka informacji o kolorze, które sugerują, że Twój kod musi utrzymać niektórych właściwości w celu zapewnienia poprawności programu:
memcpykopie count bajty z buforu źródłowego do buforu docelowego.
Bufor docelowy musi być co najmniej równym rozmiarowi buforu źródłowego.
Jednak kompilator nie może odczytać dokumentacji lub nieformalne komentarze.Nie wie, że istnieje związek między dwa bufory i count, i to również nie można skutecznie odgadnąć o relacji.SAL może zapewnić większą przejrzystość o właściwościach i wdrażania funkcji, jak pokazano poniżej:
void * memcpy(
_Out_writes_bytes_all_(count) void *dest,
_In_reads_bytes_(count) const void *src,
size_t count
);
Należy zauważyć, że adnotacje te przypominają informacje zawarte w dokumentacji MSDN, ale są one bardziej zwięzły i idą semantyczne wzór.Podczas czytania tego kodu, można szybko zrozumieć właściwości tej funkcji i jak uniknąć problemów z zabezpieczeniami przekroczenie buforu.Jeszcze lepiej semantyczne wzorce, które zapewnia SAL może poprawić skuteczność oraz efektywność kod zautomatyzowanych narzędzi do analizy w wczesnego wykrywania potencjalnych błędów.Wyobraź sobie, że ktoś zapisuje ta implementacja wadliwy wmemcpy:
wchar_t * wmemcpy(
_Out_writes_all_(count) wchar_t *dest,
_In_reads_(count) const wchar_t *src,
size_t count)
{
size_t i;
for (i = 0; i <= count; i++) { // BUG: off-by-one error
dest[i] = src[i];
}
return dest;
}
Ta implementacja zawiera typowy błąd off przez jeden.Na szczęście, kod autor wprowadził adnotacji rozmiar buforu SAL — narzędzie do analizy kodu można złapać błąd analizując ta sama funkcja.
Podstawy SAL
SAL definiuje cztery podstawowe rodzaje parametrów, które są pogrupowane według wzorek użycia.
Kategoria |
Parametr adnotacji |
Opis |
---|---|---|
Funkcja o nazwie dane wejściowe |
_In_ |
Dane są przekazywane do funkcji i jest traktowana jako tylko do odczytu. |
Dane wejściowe do funkcji o nazwie i wyjście do wywołującego |
_Inout_ |
Dane są bezużyteczne jest przekazywana do funkcji i potencjalnie jest modyfikowany. |
Dane wyjściowe do wywołującego |
_Out_ |
Obiekt wywołujący tylko miejsce wywołana funkcja do zapisu.Wywołana funkcja zapisuje dane do tego miejsca. |
Dane wyjściowe wskaźnika do wywołującego |
_Outptr_ |
Jak danych wyjściowych do wywołującego.Wartość zwracana przez funkcję o nazwie jest wskaźnikiem. |
Adnotacje te cztery podstawowe możliwe jest bardziej wyraźne na różne sposoby.Domyślnie parametry adnotacjami wskaźnika są przyjmowane jako wymagany — muszą być NIEZEROWE dla funkcji do osiągnięcia sukcesu.Najczęściej używane zmiany podstawowych adnotacje wskazuje, że parametr wskaźnika jest opcjonalne — jeśli ma wartość NULL, funkcja nadal mogą zostać obsłużone w swojej pracy.
Ta tabela przedstawia sposób rozróżnia wymaganych i opcjonalnych parametrów:
Nie są wymagane parametry |
Parametry są opcjonalne |
|
---|---|---|
Funkcja o nazwie dane wejściowe |
_In_ |
_In_opt_ |
Dane wejściowe do funkcji o nazwie i wyjście do wywołującego |
_Inout_ |
_Inout_opt_ |
Dane wyjściowe do wywołującego |
_Out_ |
_Out_opt_ |
Dane wyjściowe wskaźnika do wywołującego |
_Outptr_ |
_Outptr_opt_ |
Adnotacje te ułatwiają identyfikację możliwych wartości niezainicjowanej i nieprawidłowy wskaźnik zerowy używa w posiadanie i dokładny sposób.Przekazywaniu wartości NULL do wymaganego parametru może spowodować awarię lub może spowodować, że kod błędu "nie powiodło się" mają być zwrócone.W obu przypadkach funkcja nie uda się robi swoje zadanie.
Przykłady SAL
W tej sekcji przedstawiono przykłady kodu dla podstawowych adnotacji SAL.
Aby znaleźć wady za pomocą narzędzie do analizy kodu programu Visual Studio
W przykładach narzędzie analizy kodu Visual Studio jest używany wraz z adnotacjami SAL znaleźć kod usterki.Oto jak to zrobić.
Aby użyć narzędzia analizy kodu Visual Studio i SAL
W programie Visual Studio Otwórz projekt języka C++, który zawiera adnotacje SAL.
Na pasku menu wybierz budować, Uruchomić analizy kodu na rozwiązanie.
Rozważmy przykład _In_ w tej sekcji.To ostrzeżenie jest wyświetlane, jeśli uruchomisz analizy kodu na to:
C6387 Wartość parametru nieprawidłowy"piwo" może być "0": to nie stosować się do specyfikacji funkcji 'InCallee'.
Przykład: _In_ adnotacji
_In_ Adnotacji wskazuje, że:
Parametr musi być ważny i nie zostaną zmodyfikowane.
Funkcja będzie tylko odczyt z bufora pojedynczego elementu.
Obiekt wywołujący musi dostarczyć bufor i jego inicjowania.
_In_Określa "tylko do odczytu".Częstym błędem jest zastosowanie _In_ z parametrem, który powinien mieć _Inout_ adnotacji w zamian.
_In_jest dozwolone, ale ignorowane przez analizator o wielkości skalarne wskaźnik myszy.
void InCallee(_In_ int *pInt)
{
int i = *pInt;
}
void GoodInCaller()
{
int *pInt = new int;
*pInt = 5;
InCallee(pInt);
delete pInt;
}
void BadInCaller()
{
int *pInt = NULL;
InCallee(pInt); // pInt should not be NULL
}
Jeśli używasz analizy kodu programu Visual Studio w tym przykładzie, sprawdza czy rozmówcy przekazać wskaźnik niezerowe do zainicjowanej bufor dla pInt.W tym przypadku pInt wskaźnika nie może być ZEROWY.
Przykład: _In_opt_ adnotacji
_In_opt_jest taka sama jak _In_, z tym wyjątkiem, że parametr wejściowy może być NULL, i w związku z tym, funkcja należy sprawdzić, czy to.
void GoodInOptCallee(_In_opt_ int *pInt)
{
if(pInt != NULL) {
int i = *pInt;
}
}
void BadInOptCallee(_In_opt_ int *pInt)
{
int i = *pInt; // Dereferencing NULL pointer ‘pInt’
}
void InOptCaller()
{
int *pInt = NULL;
GoodInOptCallee(pInt);
BadInOptCallee(pInt);
}
Sprawdza poprawność analizy kodu programu Visual Studio, że funkcja sprawdza wartość NULL przed uzyskuje dostęp do buforu.
Przykład: _Out_ adnotacji
_Out_obsługuje typowy scenariusz, w którym wskaźnik NIEZEROWE, wskazujący na jej bufor element jest przekazywana i funkcja inicjuje element.Obiekt wywołujący nie ma zainicjować bufor przed wywołaniem; wywołana funkcja zapowiada przed wyświetleniem go zainicjować.
void GoodOutCallee(_Out_ int *pInt)
{
*pInt = 5;
}
void BadOutCallee(_Out_ int *pInt)
{
// Did not initialize pInt buffer before returning!
}
void OutCaller()
{
int *pInt = new int;
GoodOutCallee(pInt);
BadOutCallee(pInt);
delete pInt;
}
Narzędzie do analizy kodu Visual Studio sprawdza poprawność wywołujący zerem-do buforu dla pInt i że bufor jest inicjowany przez funkcję przed wyświetleniem.
Przykład: _Out_opt_ adnotacji
_Out_opt_jest taka sama jak _Out_, z tym wyjątkiem, że parametr może być NULL, i w związku z tym, funkcja należy sprawdzić, czy to.
void GoodOutOptCallee(_Out_opt_ int *pInt)
{
if (pInt != NULL) {
*pInt = 5;
}
}
void BadOutOptCallee(_Out_opt_ int *pInt)
{
*pInt = 5; // Dereferencing NULL pointer ‘pInt’
}
void OutOptCaller()
{
int *pInt = NULL;
GoodOutOptCallee(pInt);
BadOutOptCallee(pInt);
}
Analizy kodu Visual Studio sprawdza, że ta funkcja wyszukuje wartość NULL przed pInt zdereferencjonowano i jeśli pInt nie jest NULL, że bufor jest inicjowany przez funkcję przed wyświetleniem.
Przykład: _Inout_ adnotacji
_Inout_Służy do dodawania adnotacji parametr wskaźnika, który może być zmieniony przez funkcję.Wskaźnik musi wskazywać prawidłowe zainicjować danych przed wywołaniem, a nawet jeśli zmienia się, to muszą mieć prawidłową wartość dla powrotu.Adnotacja Określa, że funkcja może swobodnie z zapisu i odczytu do buforu pojedynczy element.Obiekt wywołujący musi dostarczyć bufor i jego inicjowania.
[!UWAGA]
Jak _Out_, _Inout_ muszą mieć zastosowanie do wartości można modyfikować.
void InOutCallee(_Inout_ int *pInt)
{
int i = *pInt;
*pInt = 6;
}
void InOutCaller()
{
int *pInt = new int;
*pInt = 5;
InOutCallee(pInt);
delete pInt;
}
void BadInOutCaller()
{
int *pInt = NULL;
InOutCallee(pInt); // ‘pInt’ should not be NULL
}
Analizy kodu Visual Studio sprawdza, że dzwoniący przekazać wskaźnik NIEZEROWE do zainicjowanej bufor dla pInti że przed zwrotu, pInt jest nadal NIEZEROWE i bufor jest inicjowany.
Przykład: _Inout_opt_ adnotacji
_Inout_opt_jest taka sama jak _Inout_, z tym wyjątkiem, że parametr wejściowy może być NULL, i w związku z tym, funkcja należy sprawdzić, czy to.
void GoodInOutOptCallee(_Inout_opt_ int *pInt)
{
if(pInt != NULL) {
int i = *pInt;
*pInt = 6;
}
}
void BadInOutOptCallee(_Inout_opt_ int *pInt)
{
int i = *pInt; // Dereferencing NULL pointer ‘pInt’
*pInt = 6;
}
void InOutOptCaller()
{
int *pInt = NULL;
GoodInOutOptCallee(pInt);
BadInOutOptCallee(pInt);
}
Analizy kodu Visual Studio sprawdza, czy ta funkcja sprawdza wartość NULL wcześniej uzyskuje dostęp do buforu, a jeśli pInt nie jest NULL, że bufor jest inicjowany przez funkcję przed wyświetleniem.
Przykład: _Outptr_ adnotacji
_Outptr_Służy do dodawania adnotacji parametr, który jest przeznaczony do zwracania wskaźnik. Parametr, sam nie powinna być równa NULL i wywołana funkcja zwraca zerem- i że wskaźnik wskazuje na zainicjować danych.
void GoodOutPtrCallee(_Outptr_ int **pInt)
{
int *pInt2 = new int;
*pInt2 = 5;
*pInt = pInt2;
}
void BadOutPtrCallee(_Outptr_ int **pInt)
{
int *pInt2 = new int;
// Did not initialize pInt buffer before returning!
*pInt = pInt2;
}
void OutPtrCaller()
{
int *pInt = NULL;
GoodOutPtrCallee(&pInt);
BadOutPtrCallee(&pInt);
}
Sprawdza poprawność analizy kodu Visual Studio wywołujący zerem- *pInt, oraz że bufor jest inicjowany przez funkcję przed wyświetleniem.
Przykład: _Outptr_opt_ adnotacji
_Outptr_opt_jest taka sama jak _Outptr_, z tym wyjątkiem, że parametr jest opcjonalny — obiekt wywołujący może przejść w wskaźnik o wartości NULL dla parametru.
void GoodOutPtrOptCallee(_Outptr_opt_ int **pInt)
{
int *pInt2 = new int;
*pInt2 = 6;
if(pInt != NULL) {
*pInt = pInt2;
}
}
void BadOutPtrOptCallee(_Outptr_opt_ int **pInt)
{
int *pInt2 = new int;
*pInt2 = 6;
*pInt = pInt2; // Dereferencing NULL pointer ‘pInt’
}
void OutPtrOptCaller()
{
int **ppInt = NULL;
GoodOutPtrOptCallee(ppInt);
BadOutPtrOptCallee(ppInt);
}
Analizy kodu Visual Studio sprawdza, że ta funkcja wyszukuje wartość NULL przed *pInt zdereferencjonowano, oraz że bufor jest inicjowany przez funkcję przed wyświetleniem.
Przykład: Adnotacja _Success_ w połączeniu z _Out_
Adnotacje mogą być stosowane do większości obiektów. W szczególności można dodać adnotacje całej funkcji. Jest jedną z najbardziej oczywistych cech funkcji, że odnieść sukces lub niepowodzenie.Ale jak skojarzenie między buforu i jego rozmiar, C/C++ nie express funkcja sukces lub niepowodzenie.Za pomocą _Success_ adnotacji, można powiedzieć, co sukcesem dla funkcji wygląda jak. Parametr do _Success_ adnotacji jest po prostu wyrażeniem, kiedy to PRAWDA, wskazuje że funkcja została pomyślnie.Wyrażenie może być cokolwiek, jaką może obsłużyć parser adnotacji.Skutki adnotacje po funkcja zwraca są stosowane tylko w przypadku, gdy funkcja się powiedzie.W tym przykładzie przedstawiono sposób _Success_ współdziała z _Out_ to, co trzeba zrobić.Można użyć słowa kluczowego return do reprezentowania wartości zwracanej.
_Success_(return != false) // Can also be stated as _Success_(return)
bool GetValue(_Out_ int *pInt, bool flag)
{
if(flag) {
*pInt = 5;
return true;
} else {
return false;
}
}
_Out_ Adnotacji powoduje analizy kodu Visual Studio do sprawdzania poprawności wywołujący zerem-do buforu dla pInt, oraz że bufor jest inicjowany przez funkcję przed wyświetleniem.
SAL najlepszych praktyk
Dodawanie adnotacji do istniejącego kodu
SAL to potężne technologia, która może pomóc poprawić bezpieczeństwo i niezawodność kodu.Po nauczyć SAL, nowych umiejętności można zastosować do codziennej pracy.W nowy kod można stosować specyfikacje oparte na SAL zgodnie z projektem przez cały; w starszych kodu można stopniowo dodawać adnotacje i tym samym zwiększyć korzyści za każdym razem, gdy aktualizacji.
Microsoft publiczne nagłówki są już odnotowany.Sugerujemy w związku z tym, że w projektach najpierw opisywania funkcji węzła liścia i funkcje, które wywołują Win32 API, aby odnieść największe korzyści.
Kiedy adnotacje
Poniżej przedstawiono kilka wskazówek:
Opisywanie wszystkie parametry wskaźnika.
Opisywanie adnotacje zakres wartości, aby umożliwić analizy kodu buforu i wskaźnik bezpieczeństwa.
Opisywanie reguł blokowania i blokowania niepożądanych.Aby uzyskać więcej informacji, zobacz Dodawanie adnotacji do zachowania blokującego.
Opisywanie właściwości sterownika i inne właściwości specyficzne dla domeny.
Lub można opatrzyć wszystkie parametry upewnij się jasno konwersji całej i aby łatwo sprawdzić, że adnotacje zostały wykonane.
Zasoby pokrewne
Zobacz też
Informacje
Dodawanie adnotacji do parametrów funkcji i zwracanych wartości
Zachowanie funkcji dodawania adnotacji
Dodawanie adnotacji struktur i klas
Dodawanie adnotacji do zachowania blokującego
Określanie warunków pojawiania się adnotacji
Najlepsze praktyki i przykłady (SAL)
Inne zasoby
Korzystanie z adnotacji SAL w celu redukowanie defektów kodu C/C++