Coraz popularniejsze stają się bezpieczne języki programowania (PL). Zastanawiam się, jaka jest formalna definicja bezpiecznej PL. Na przykład C nie jest bezpieczny, ale Java jest bezpieczna. Podejrzewam, że właściwość „bezpieczna” powinna być stosowana do implementacji PL, a nie do samej PL. Jeśli tak, omówmy definicję bezpiecznej implementacji PL. Moje własne próby sformalizowania tego pojęcia doprowadziły do dziwnego wyniku, dlatego chciałbym usłyszeć inne opinie. Nie mów, że każde PL ma niebezpieczne polecenia. Zawsze możemy wziąć bezpieczny podzbiór.
programming-languages
beroal
źródło
źródło
Odpowiedzi:
Kiedy nazywamy język „bezpiecznym” pod pewnym względem , oznacza to formalnie, że istnieje dowód, że żaden dobrze sformułowany program w języku nie może zrobić czegoś, co uważamy za niebezpieczne. Słowo „bezpieczny” jest również używane mniej formalnie, ale to właśnie tutaj ludzie rozumieją twoje pytanie. Istnieje wiele różnych definicji właściwości, które chcemy mieć w „bezpiecznym” języku.
Kilka ważnych to:
Definicja „dźwięczności typu” Andrew Wrighta i Matthiasa Felleisena , przywoływana w wielu miejscach (w tym w Wikipedii) jako przyjęta definicja „bezpieczeństwa typu”, oraz ich dowód z 1994 r., Że podzbiór ML spełnia ten warunek.
Michael Hicks wymienia tutaj kilka definicji „bezpieczeństwa pamięci” . Niektóre są listami rodzajów błędów, które nie mogą wystąpić, a niektóre polegają na traktowaniu wskaźników jako możliwości. Java gwarantuje, że żaden z tych błędów nie jest możliwy (chyba że wyraźnie zaznaczysz funkcję oznaczoną
unsafe
), ponieważ moduł śmieciowy zarządza wszystkimi alokacjami i dezalokacjami. Rust daje taką samą gwarancję (ponownie, chyba że wyraźnie zaznaczysz kod jakounsafe
), poprzez swój system typów afinicznych, który wymaga, aby zmienna była własnością lub była wypożyczona przed użyciem co najmniej raz.Podobnie kod bezpieczny dla wątków jest zwykle definiowany jako kod, który nie może wykazywać pewnych rodzajów błędów dotyczących wątków i pamięci współdzielonej, w tym wyścigów danych i zakleszczeń. Te właściwości są często wymuszane na poziomie językowym: Rust gwarantuje, że wyścigi danych nie mogą wystąpić w systemie typów, C ++ gwarantuje, że
std::shared_ptr
inteligentne wskaźniki do tych samych obiektów w wielu wątkach nie usuną obiektu przedwcześnie lub nie skasują go podczas ostatniego odwołania aby go zniszczyć, C i C ++ mają dodatkowoatomic
zmienne wbudowane w język, a operacje atomowe gwarantują wymuszenie pewnych rodzajów spójności pamięci, jeśli są używane poprawnie. MPI ogranicza komunikację międzyprocesową do jawnych komunikatów, a OpenMP ma składnię zapewniającą bezpieczny dostęp do zmiennych z różnych wątków.Właściwość, której pamięć nigdy nie wycieknie, jest często nazywana „bezpieczną przestrzenią”. Automatyczne zbieranie śmieci jest jedną z funkcji językowych, które to zapewniają.
Wiele języków ma gwarancję, że jego działania będą miały dobrze określone wyniki, a programy będą dobrze zachowane. Jak supercat podał przykład powyższego, C robi to dla arytmetyki bez znaku (gwarantowane bezpieczne zawijanie), ale nie podpisanej arytmetyki (gdzie przepełnienie może powodować dowolne błędy, ponieważ C musiało obsługiwać procesory, które robią różne rzeczy po podpisaniu arytmetyki przepełnienia), ale wtedy język czasami cicho konwertuje niepodpisane ilości na podpisane.
Języki funkcjonalne mają dużą liczbę niezmienników, które każdy dobrze uformowany program gwarantuje, na przykład, że czyste funkcje nie mogą powodować skutków ubocznych. Mogą one, ale nie muszą być określane jako „bezpieczne”.
Niektóre języki, takie jak SPARK lub OCaml, zostały zaprojektowane w celu ułatwienia sprawdzenia poprawności programu. To może, ale nie musi być opisane jako „bezpieczne” od błędów.
Dowody na to, że system nie może naruszać formalnego modelu bezpieczeństwa (stąd powiedzenie: „Każdy system, który jest prawdopodobnie bezpieczny, prawdopodobnie nie jest”)
źródło
Nie ma formalnej definicji „bezpiecznego języka programowania”; to nieformalne pojęcie. Języki, które twierdzą, że zapewniają bezpieczeństwo, zwykle zawierają dokładne formalne oświadczenie o tym, jakiego rodzaju bezpieczeństwo jest żądane / gwarantowane / zapewniane. Na przykład język może zapewniać bezpieczeństwo typu, bezpieczeństwo pamięci lub inną podobną gwarancję.
źródło
double
lublong
podczas, gdy jest modyfikowana w innym wątku, co nie gwarantuje, że nie zostanie wytworzona połowa jednej wartości zmieszana w jakiś nieokreślony sposób z połową innego), ale specyfikacja API jednak w niektórych przypadkach ma nieokreślone zachowanie.Jeśli możesz dostać kopię Typów i języków programowania Benjamina Pierce'a , we wstępie ma dobry przegląd różnych perspektyw na temat „bezpiecznego języka”.
Jedną z proponowanych interpretacji tego terminu, która może Cię zainteresować, jest:
Wahałbym się więc przed użyciem terminu „niebezpieczne” w odniesieniu do implementacji języka programowania. Jeśli niezdefiniowany termin w języku powoduje odmienne zachowanie w różnych implementacjach, jedna z implementacji może zapewnić zachowanie, które może być bardziej oczekiwane, ale nie nazwałbym tego „bezpiecznym”.
źródło
Sejf nie jest binarny, to kontinuum .
Mówiąc nieformalnie, bezpieczeństwo oznacza sprzeciw wobec błędów, przy czym 2 najczęściej wymieniane to:
Nie są to jedyne klasy błędów, którym zapobiegają języki, wolność wyścigów danych lub swoboda impasu jest raczej pożądana, dowody poprawności są całkiem słodkie itp.
Po prostu niepoprawne programy rzadko są jednak uważane za „niebezpieczne” (tylko zawierają błędy), a termin „bezpieczeństwo” jest zasadniczo zarezerwowany dla gwarancji wpływających na naszą zdolność rozumowania programu. Zatem C, C ++ lub Go, posiadające niezdefiniowane zachowanie, są niebezpieczne.
Oczywiście istnieją języki z niebezpiecznymi podzbiorami (Java, Rust, ...), które celowo wyznaczają obszary, w których programista jest odpowiedzialny za utrzymanie gwarancji językowych, a kompilator jest w trybie „hands-off”. Języki nadal są ogólnie nazywane bezpiecznymi , pomimo tego luku ratunkowego, pragmatycznej definicji.
źródło
Obj.magic
W Ocaml). A w praktyce są one naprawdę potrzebneChociaż nie zgadzam się z odpowiedzią DW, myślę, że pozostawia ona część „bezpiecznej” bez odpowiedzi.
Jak wspomniano, promowanych jest wiele rodzajów bezpieczeństwa. Uważam, że dobrze jest zrozumieć, dlaczego istnieje wiele pojęć. Każde pojęcie wiąże się z ideą, że programy cierpią szczególnie na pewną klasę błędów i że programiści nie byliby w stanie popełnić tego rodzaju błędu, gdyby język tego nie blokował.
Należy zauważyć, że te różne pojęcia mają różne klasy błędów i klasy te nie wykluczają się wzajemnie, ani nie obejmują one wszystkich form błędów. Na przykład 2 przykłady DW, pytanie, czy określona lokalizacja pamięci zawiera określony obiekt, jest zarówno kwestią bezpieczeństwa typu, jak i bezpieczeństwa pamięci.
Dalsza krytyka „bezpiecznych języków” wynika z obserwacji, że zakazanie niektórych konstrukcji uważanych za niebezpieczne pozostawia programistom potrzebę wymyślenia alternatyw. Empirycznie bezpieczeństwo jest lepiej osiągane dzięki dobrym bibliotekom. użycie kodu, który został już przetestowany w terenie, pozwala uniknąć nowych błędów.
źródło
Podstawowa różnica między C i Javą polega na tym, że jeśli uniknie się pewnych łatwych do zidentyfikowania cech Javy (np. W
Unsafe
przestrzeni nazw), każda możliwa akcja, którą można podjąć - w tym „błędne” - będzie miała ograniczony zakres możliwych wyników . Wprawdzie ogranicza to to, co można zrobić w Javie - przynajmniej bez korzystania zUnsafe
przestrzeni nazw, ale umożliwia także ograniczenie szkód, które mogą być spowodowane przez błędny program lub - co ważniejsze - przez program, który poprawnie przetworzyłby prawidłowe pliki, ale nie są szczególnie chronione przed błędami.Tradycyjnie kompilatory C przetwarzałyby wiele akcji w sposób zdefiniowany w standardzie w „normalnych” przypadkach, podczas gdy przetwarzałyby wiele przypadków narożnych „w sposób charakterystyczny dla środowiska”. Jeśli ktoś używałby procesora, który zwarłby się i zapaliłby, gdyby nastąpiło przepełnienie numeryczne, i chciałby uniknąć zapłonu procesora, musiałby napisać kod, aby uniknąć przepełnienia numerycznego. Gdyby jednak ktoś używał procesora, który idealnie szczęśliwie obcinałby wartości w uzupełnieniu do dwóch, nie trzeba unikać przepełnień w przypadkach, w których takie obcięcie skutkowałoby akceptowalnym zachowaniem.
Nowoczesne C idzie o krok dalej: nawet jeśli ktoś celuje w platformę, która w naturalny sposób zdefiniowałaby zachowanie czegoś takiego jak przepełnienie numeryczne, w którym Standard nie nakładałby żadnych wymagań, przepełnienie jednej części programu może wpłynąć na zachowanie innych części programować w sposób arbitralny, niezwiązany prawami czasu i przyczynowości. Rozważmy na przykład coś takiego:
„Nowoczesny” kompilator języka C otrzymujący coś takiego jak powyższy może dojść do wniosku, że ponieważ obliczenia x * x przepełniłyby się, gdyby x był większy niż 46340, może bezwarunkowo wykonać wywołanie „foo”. Zauważ, że nawet jeśli akceptowalne byłoby nienormalne zakończenie działania programu, jeśli x jest poza zakresem, lub funkcja zwraca jakąkolwiek wartość w takich przypadkach, wywołanie foo () z poza zakresem x może spowodować szkody daleko poza jedna z tych możliwości. Tradycyjne C nie zapewniałoby żadnego sprzętu zabezpieczającego poza tym, co dostarczał programista i platforma, ale pozwoliłoby, aby sprzęt zabezpieczający ograniczył szkody w nieoczekiwanych sytuacjach. Nowoczesne C ominie każdy sprzęt bezpieczeństwa, który nie jest w 100% skuteczny w utrzymywaniu wszystkiego pod kontrolą.
źródło
int
ma 32 bity,x
zostanie awansowany do podpisanegoint
. Sądząc z uzasadnieniem, autorzy standardu oczekiwać, że nie dziwne implementacje potraktuje podpisane i niepodpisane typy w równoważny sposób poza niektórych szczególnych przypadkach, ale gcc czasami „optymalizuje” w taki sposób, że łamią jeżeliuint16_t
przezuint16_t
mnożenie daje wynik poza INT_MAX , nawet jeśli wynik jest używany jako wartość bez znaku.-Wconversion
.return x+1;
których nie powinno to być problematyczne, a przekazanie wyniku na uint32_t stłumiłoby komunikat bez rozwiązania problemu.Istnieje kilka warstw poprawności w języku. W kolejności rosnącej abstrakcji:
Na kolejnym poziomie błędy wykryte w czasie kompilacji zamiast w czasie wykonywania zwiększają bezpieczeństwo języka. Program poprawny pod względem składniowym powinien również być poprawny semantycznie w jak największym stopniu. Oczywiście kompilator nie zna dużego obrazu, więc dotyczy to poziomu szczegółowości. Silne i wyraziste typy danych są jednym z aspektów bezpieczeństwa na tym poziomie. Można powiedzieć, że język powinien utrudniać pewne rodzaje błędów(błędy typu, dostęp poza granicami, niezainicjowane zmienne itp.). Informacje o typie wykonawczym, takie jak tablice zawierające informacje o długości, pozwalają uniknąć błędów. Zaprogramowałem Ada 83 na studiach i odkryłem, że kompilujący program Ada zwykle zawierał może o rząd wielkości mniej błędów niż odpowiedni program C. Wystarczy wziąć pod uwagę zdolność Ady do definiowania przez użytkownika typów liczb całkowitych, których nie można przypisać bez wyraźnej konwersji: Całe statki kosmiczne uległy awarii, ponieważ mylono stopy i metry, których można było trywialnie uniknąć w przypadku Ady.
Na następnym poziomie język powinien zapewniać środki pozwalające uniknąć kodu typu „Booster”. Jeśli musisz napisać własne pojemniki, ich sortowanie, ich konkatenację, lub jeśli musisz napisać własny
string::trim()
, popełnisz błędy. Ponieważ poziom abstrakcji podnosi się, kryteria te dotyczą zarówno właściwego języka, jak i standardowej biblioteki języka.Obecnie język powinien zapewniać środki do równoczesnego programowania na poziomie językowym. Współbieżność jest trudna do osiągnięcia i być może jest niemożliwa do prawidłowego wykonania bez obsługi języka.
Język powinien zapewniać środki do modularyzacji i współpracy. Silne, rozbudowane, zdefiniowane przez użytkownika typy powyżej pomagają tworzyć ekspresyjne interfejsy API.
Nieco ortogonalnie definicja języka powinna być zrozumiała; język i biblioteki powinny być dobrze udokumentowane. Zła lub brakująca dokumentacja prowadzi do złych i niewłaściwych programów.
1 Ponieważ jednak zwykle nie można udowodnić poprawności maszyny wirtualnej, takie języki mogą nieco paradoksalnie nie być odpowiednie dla bardzo surowych wymagań bezpieczeństwa.
źródło
Każdy język, który znam, ma sposoby pisania nielegalnych programów, które można (kompilować i) uruchamiać. I każdy język, który znam, ma bezpieczny podzbiór. Więc jakie jest twoje pytanie?
Bezpieczeństwo jest wielowymiarowe i subiektywne.
Niektóre języki mają wiele operacji, które są „niebezpieczne”. Inni mają mniej takich operacji. W niektórych językach domyślny sposób robienia czegoś jest z natury niebezpieczny. W innych domyślny sposób jest bezpieczny. W niektórych językach istnieje wyraźny podzbiór „niebezpieczny”. W innych językach nie ma takiego podzbioru.
W niektórych językach „bezpieczeństwo” odnosi się wyłącznie do bezpieczeństwa pamięci - usługa oferowana przez standardową bibliotekę i / lub środowisko wykonawcze, w którym naruszenia dostępu do pamięci są utrudnione lub niemożliwe. W innych językach „bezpieczeństwo” wyraźnie obejmuje bezpieczeństwo wątków. W innych językach „bezpieczeństwo” oznacza gwarancję, że program się nie zawiesi (wymaganie to obejmuje niedopuszczanie nieprzechwyconych wyjątków). Wreszcie, w wielu językach „bezpieczeństwo” odnosi się do bezpieczeństwa typu - jeśli system typów jest pod pewnymi względami spójny, mówi się, że jest „dźwiękiem” (nawiasem mówiąc, Java i C # nie mają całkowicie dźwiękowych systemów typów).
W niektórych językach wszystkie różne znaczenia „bezpieczeństwa” są uważane za podzbiory bezpieczeństwa typu (np. Rust i Pony osiągają bezpieczeństwo gwintu dzięki właściwościom systemu typów).
źródło
Ta odpowiedź jest nieco szersza. Słowa „bezpieczeństwo i ochrona” zostały w ostatnich dziesięcioleciach okaleczone przez niektóre zorientowane politycznie części społeczeństwa anglojęzycznego, tak że ich powszechne użycie prawie nie ma definicji. Jednak w przypadku przedmiotów technicznych wciąż wracam do definiowania „bezpieczeństwa” i „bezpiecznego” jako: urządzenia, które zapobiega niezamierzonemu użyciu czegoś lub znacznie utrudnia przypadkowe użycie, a stan będący pod ochroną takiego urządzenia .
Tak więc bezpieczny język ma pewne urządzenie, które ogranicza konkretną klasę błędów. Oczywiście limity wiążą się w niektórych przypadkach z niedogodnościami, a nawet niemożnością, a to nie znaczy, że „niebezpieczne” języki będą powodować błędy. np. nie mam bezpiecznych korków na widelcach i od dziesięcioleci udało mi się, bez większego wysiłku, uniknąć zakleszczenia oka podczas jedzenia. Z pewnością mniej wysiłku niż przy użyciu korków. Bezpieczeństwo wiąże się z pewnymi kosztami, w stosunku do których należy je oceniać. (widelec korkowy jest odniesieniem do postaci Steve'a Martina)
źródło