Czy informacje ukrywają coś więcej niż konwencję?

20

W Javie, C # i wielu innych silnie typowanych, sprawdzanych statycznie językach, jesteśmy przyzwyczajeni do pisania takiego kodu:

public void m1() { ... }
protected void m2() { ... }
private void m2() { ... }
void m2() { ... }

Niektóre dynamicznie sprawdzane języki nie zapewniają słów kluczowych do wyrażenia poziomu „prywatności” danego członka klasy i polegają na konwencjach kodowania. Na przykład Python poprzedza członków prywatnych znakiem podkreślenia:

_m(self): pass

Można argumentować, że udostępnianie takich słów kluczowych w dynamicznie sprawdzanych językach przydałoby się niewiele, ponieważ jest sprawdzane tylko w czasie wykonywania.

Nie mogę też znaleźć dobrego powodu, aby podać te słowa kluczowe w językach sprawdzonych statycznie. Uważam, że muszę wypełnić mój kod dość szczegółowymi słowami kluczowymi, takimi protectedjak irytujące i rozpraszające. Do tej pory nie byłem w sytuacji, w której błąd kompilatora spowodowany tymi słowami kluczowymi uratowałby mnie przed błędem. Wręcz przeciwnie, byłem w sytuacjach, w których błędnie umieszczone protecteduniemożliwiło mi korzystanie z biblioteki.

Mając to na uwadze, moje pytanie brzmi:

Czy informacje ukrywają coś więcej niż konwencję między programistami używaną do zdefiniowania, co jest częścią oficjalnego interfejsu klasy?

Czy można go wykorzystać do zabezpieczenia tajnego stanu klasy przed atakiem? Czy odbicie może zastąpić ten mechanizm? Co byłoby warte wymuszenia ukrywania informacji przez kompilator ?

blubb
źródło
10
Przepraszamy, Eric Lippert jest na wakacjach :)
Benjol,
Twoje pytanie jest nieco trudne do zrozumienia. Czy po prostu pytasz, czy ktoś może uzyskać dostęp do prywatnego członka z zewnątrz?
Morgan Herlocker,
1
Powiązane pytanie: programmers.stackexchange.com/questions/92380/…
user281377,
@ironcode: Mogę powiedzieć, że jakoś nie wyrażam się wystarczająco jasno z niektórych odpowiedzi. Czy możesz powiedzieć, co konkretnie utrudnia zrozumienie? Spróbuję to wyjaśnić.
blubb
2
„Bezsensowne jest podawanie informacji w czasie kompilacji, a następnie sprawdzanie ich tylko w czasie wykonywania” - naprawdę? Lepiej powiedz to Microsoftowi, ponieważ na tym opiera się znaczna część obecnego frameworku .NET: wybiórcze włączanie lub rezygnacja z pisania statycznego. Nie sądzę jednak, że tak naprawdę masz na myśli; pytanie wielokrotnie mówi o sprawdzaniu „statycznym” vs. „dynamicznym”, ale są to cechy charakterystyczne systemu typów; co tak naprawdę chodzi tu jest kompilacji vs. run-time wiążące .
Aaronaught,

Odpowiedzi:

4

Studiuję do certyfikacji Java i cała jej masa dotyczy tego, jak zarządzać modyfikatorami dostępu. I mają sens i powinny być właściwie używane.

Pracowałem z Pythonem i podczas mojej podróży edukacyjnej słyszałem, że w Pythonie konwencja istnieje, ponieważ ludzie, którzy z nim pracują, powinni wiedzieć, co to znaczy i jak go stosować. To powiedziawszy, _m(self): passpodkreślenie ostrzegłoby mnie, żebym nie zadzierał z tym polem. Ale czy wszyscy będą przestrzegać tej konwencji? Pracuję z javascript i muszę powiedzieć, że nie . Czasami muszę sprawdzić problem, a przyczyną było to, że osoba robi coś, czego nie powinna robić ...

przeczytaj tę dyskusję dotyczącą wiodącego podkreślenia w pythonie

Czy informacje ukrywają coś więcej niż konwencję między programistami używaną do zdefiniowania, co jest częścią oficjalnego interfejsu klasy?

Z tego, co powiedziałem: tak.

Czy można go wykorzystać do zabezpieczenia tajnego stanu klasy przed atakiem? Czy odbicie może zastąpić ten mechanizm? Czy są jakieś inne zalety zapewniane przez bardziej formalny mechanizm egzekwowany przez kompilator?

Tak, może być użyty do zabezpieczenia tajnego stanu klasy i powinien być nie tylko używany do tego, ale także aby uniemożliwić użytkownikom bałaganowanie stanu obiektów w celu zmiany ich zachowań na coś, co według nich powinno być takie, jak obiekt powinien mieć pracowałem. Jako deweloper powinieneś o tym pomyśleć i zaprojektować swoją klasę w sposób, który ma sens, aby jej zachowanie nie zostało naruszone. A kompilator, jako dobry przyjaciel, pomoże ci zachować kod w sposób, w jaki go zaprojektowałeś, egzekwując zasady dostępu.

EDYTOWANE Tak, refleksja może, sprawdź komentarze

Ostatnie pytanie jest interesujące i chętnie przeczytam dotyczące go odpowiedzi.

wleao
źródło
3
Odbicie zwykle może ominąć ochronę dostępu. W przypadku Java i C # oba mogą uzyskać dostęp do prywatnych danych.
Mark H
Dzięki! Odpowiedzi tutaj: stackoverflow.com/questions/1565734/... wyjaśnij to!
wleao
1
Kultura JavaScript różni się bardzo od kultury Pythona. Python ma pep-08 i prawie wszyscy programiści python przestrzegają tych konwencji. Naruszenia PEP-08 są często uważane za błąd. Tak więc myślę, że javascript / php / perl / ruby ​​experience w niewielkim stopniu przekłada się na doświadczenie w Pythonie:
Mike Korobov,
1
W niektórych (ale nie wszystkich) językach programowania ukrywanie informacji może służyć do ochrony przed wrogim kodem. Ukrywania informacji nie można wykorzystywać do ochrony przed wrogimi użytkownikami.
Brian
2
@Mike, jeśli naruszenia PEP-08 są tak często „uważane za błąd”, a żaden twórca Pythona nie marzyłby o nieprzestrzeganiu konwencji, równie dobrze możesz egzekwować je przez język! Dlaczego służba działa, skoro język może to zrobić automatycznie?
Andres F.,
27

Specyfikator dostępu „prywatny” nie dotyczy błędu kompilatora, który generuje za pierwszym razem. W rzeczywistości chodzi o uniemożliwienie ci dostępu do czegoś, co wciąż może ulec zmianie, gdy zmieni się implementacja klasy przechowującej członka prywatnego.

Innymi słowy, niedopuszczenie do użycia go, gdy nadal działa, zapobiega przypadkowemu użyciu go, gdy nie działa.

Jak zauważył Delnan poniżej, konwencja prefiksowa odradza przypadkowe użycie członków, którzy mogą ulec zmianie, o ile konwencja jest przestrzegana i poprawnie rozumiana. W przypadku złośliwego (lub nieświadomego) użytkownika nic nie stoi na przeszkodzie, aby uzyskać dostęp do tego członka ze wszystkimi możliwymi konsekwencjami. W językach z wbudowaną obsługą specyfikatorów dostępu nie dzieje się to w niewiedzy (błąd kompilatora) i wyróżnia się jak obolały kciuk, gdy jest złośliwy (dziwne konstrukcje, aby dostać się do członka prywatnego).

„Chroniony” specyfikator dostępu to inna historia - nie myśl o tym jako o „niezupełnie publicznym” lub „bardzo prywatnym”. „Chroniony” oznacza, że ​​prawdopodobnie będziesz chciał skorzystać z tej funkcji, gdy wywodzisz się z klasy zawierającej chroniony element członkowski. Chronieni członkowie są częścią „interfejsu rozszerzenia”, którego użyjesz do dodania funkcjonalności nad istniejącymi klasami bez zmiany samych istniejących klas.

Podsumowując:

  • public: Używaj bezpiecznie w instancjach klasy, cel klasy nie zmieni się.
  • chroniony: Używany podczas rozszerzania (wywodzenia) klasy - może ulec zmianie, jeśli implementacja musi się drastycznie zmienić.
  • prywatny: Nie dotykaj! Może się zmieniać do woli, aby zapewnić lepszą implementację oczekiwanych interfejsów.
Joris Timmermans
źródło
3
Co ważniejsze, jakakolwiek (łatwo rozpoznawalna, jak ma to miejsce np. W przypadku wiodących znaków podkreślenia) niewymuszona konwencja o „szczegółach implementacji, z zastrzeżeniem zmiany” pozwala również uniknąć przypadkowego wykorzystania członków prywatnych. Na przykład w Pythonie jedyny rozsądny programista pisze kod za pomocą prywatnego członka z zewnątrz (a nawet wtedy marszczy brwi), kiedy jest to prawie nieuniknione, wie dokładnie, co robi, i akceptuje potencjalne zepsucie w przyszłych wersjach . To jest sytuacja, w której np. Programiści Java używają refleksji.
3
@ Simon Stelling - nie, mówię, że oryginalny implementator klasy, który uczynił coś prywatnym, mówi ci „nie używaj tego, każde zachowanie tutaj może się zmienić w przyszłości”. Na przykład zmienna prywatna członka klasy, która najpierw zawiera odległość między dwoma punktami, może później zawierać odległość między dwoma punktami do kwadratu ze względu na wydajność. Wszystko, co opierałoby się na starym zachowaniu, rozpadłoby się po cichu.
Joris Timmermans,
1
@MadKeithV: W jaki sposób słowo kluczowe na poziomie języka, mówiąc „nie dotykaj, ponieważ ...” różni się od konwencji oznaczającej to samo? Wymusza to tylko kompilator, ale wracamy do starej dyskusji statycznej vs. dynamicznej.
7
@ Simon Stelling (i Delnan) - dla mnie to trochę jak pytanie „czym różni się ogranicznik silnika od znaku ograniczenia prędkości”.
Joris Timmermans,
1
Jestem świadomy tej różnicy i nigdy nie powiedziałem, że jej nie ma. Po prostu stwierdzam, że twoja odpowiedź i większość twoich komentarzy uzupełniających opisują jedynie rodzaje adnotacji dla członków i metod, które można wprowadzić za pomocą konwencji lub słów kluczowych na poziomie języka, i pozostawić faktyczną różnicę (obecność wymuszania na poziomie języka) jako wywnioskowany przez czytelnika.
11

Jeśli piszesz kod, który będzie używany przez kogoś innego, ukrywanie informacji może zapewnić znacznie łatwiejszy do zrozumienia interfejs. „Ktoś inny” może być innym programistą w twoim zespole, programistami korzystającymi z interfejsu API, który napisałeś komercyjnie, a nawet przyszłym sobą, który „po prostu nie pamięta, jak to działa. O wiele łatwiej jest pracować z klasą, która ma tylko 4 dostępne metody niż jedna, która ma 40.

Morgan Herlocker
źródło
2
Całkowicie się zgadzam, ale to nie odpowiada na moje pytanie. To, czy używam, _memberczy private memberw mojej klasie, jest nieistotne, o ile inny programista rozumie, że powinien patrzeć tylko na publicczłonków bez prefiksu.
blubb
6
Tak, ale publiczna dokumentacja klasy napisana w dynamicznym języku również nie rzuca na ciebie tych 36 prywatnych części (przynajmniej domyślnie).
3
@ Simon Stwierdzenie, że jest to „tylko konwencja”, jest mylące, ponieważ przynosi realne korzyści. „Po prostu konwencja” to coś w rodzaju nawiasów klamrowych w następnym wierszu. Konwencja z korzyściami przypomina używanie znaczących nazw, które, jak powiedziałbym, są zupełnie inne. Czy ustawienie czegoś prywatnego uniemożliwia komuś zobaczenie „tajnych metod”? Nie, nie jeśli zostaną ustalone.
Morgan Herlocker,
1
@ Simon- również ujawnienie tylko metod bezpiecznych w użyciu zwiększa prawdopodobieństwo, że użytkownik niczego nie zepsuje podczas próby użycia klasy. Można łatwo zastosować metody, które mogą spowodować awarię serwera WWW, jeśli zostaną wywołane tysiąc razy. Nie chcesz, aby konsumenci Twojego API mogli to robić. Abstrakcja jest czymś więcej niż tylko konwencją.
Morgan Herlocker,
4

Sugerowałbym, aby w tle zacząć od przeczytania o niezmiennikach klasowych .

Niezmiennikiem jest, mówiąc krótko, założenie o stanie klasy, które ma pozostać prawdziwe przez cały czas jej trwania.

Użyjmy bardzo prostego przykładu w języku C #:

public class EmailAlert
{
    private readonly List<string> addresses = new List<string>();

    public void AddRecipient(string address)
    {
        if (!string.IsNullOrEmpty(address))
            addresses.Add(address);
    }

    public void Send(string message)
    {
        foreach (string address in addresses)
            SendTo(address, message);
    }

    // Details of SendTo not shown
}

Co tu się dzieje?

  • Element ten addressesjest inicjowany w konstrukcji klasy.
  • Jest prywatny, więc nic z zewnątrz nie może go dotknąć.
  • Zrobiliśmy to readonly, więc nic od wewnątrz nie może go dotknąć po zakończeniu budowy (nie zawsze jest to poprawne / konieczne, ale jest przydatne tutaj).
  • Dlatego Sendmetoda może przyjąć założenie, że addressesnigdy nie będzie null. Nie musi wykonywać tej kontroli, ponieważ nie ma możliwości zmiany wartości.

Jeśli pozwolono by innym klasom pisać w addressespolu (tj. Gdyby tak było public), to założenie nie byłoby już aktualne. Każda inna metoda w klasie, która zależy od tego pola, musiałaby zacząć wykonywać jawne kontrole zerowe lub grozić awarią programu.

Więc tak, to znacznie więcej niż „konwencja”; wszystkie modyfikatory dostępu członków klasy wspólnie tworzą zestaw założeń dotyczących tego, kiedy i jak ten stan można zmienić. Założenia te są następnie włączane do zależnych i współzależnych członków i klas, aby programiści nie musieli rozumować o całym stanie programu w tym samym czasie. Umiejętność przyjmowania założeń jest kluczowym elementem zarządzania złożonością oprogramowania.

Na te inne pytania:

Czy można go wykorzystać do zabezpieczenia tajnego stanu klasy przed atakiem?

Tak i nie. Kod bezpieczeństwa, podobnie jak większość kodów, będzie polegał na pewnych niezmiennikach. Modyfikatory dostępu są z pewnością pomocne jako drogowskazy dla zaufanych dzwoniących, których nie powinni z tym zadzierać. Szkodliwy kod nie ma znaczenia, ale złośliwy kod również nie musi przechodzić przez kompilator.

Czy odbicie może zastąpić ten mechanizm?

Oczywiście że tak. Ale refleksja wymaga, aby kod wywołujący miał ten poziom uprawnień / zaufania. Jeśli używasz złośliwego kodu z pełnym zaufaniem i / lub uprawnieniami administracyjnymi, oznacza to, że przegrałeś już tę bitwę.

Co byłoby warte wymuszenia ukrywania informacji przez kompilator?

Kompilator już to wymusza. Podobnie jest w środowisku uruchomieniowym w .NET, Javie i innych takich środowiskach - kod operacji użyty do wywołania metody nie powiedzie się, jeśli metoda jest prywatna. Jedyne sposoby obejścia tego ograniczenia wymagają zaufanego / podwyższonego kodu, a podniesiony kod zawsze może po prostu zapisywać bezpośrednio w pamięci programu. Jest egzekwowany w takim stopniu, w jakim można go egzekwować bez wymagania niestandardowego systemu operacyjnego.

Aaronaught
źródło
1
@ Simon: Musimy tu wyjaśnić „ignorancję”. Prefiks metody za pomocą metody _określa kontrakt, ale go nie egzekwuje. Może to utrudniać nieznajomość umowy, ale nie utrudnia jej zerwania , szczególnie w porównaniu z żmudnymi i często restrykcyjnymi metodami, takimi jak Reflection. Konwencja mówi: „nie powinieneś tego łamać”. Modyfikator dostępu mówi „nie możesz tego złamać”. Ja nie chce powiedzieć, że jedno podejście jest lepsze niż inne - są one zarówno grzywny w zależności od filozofii, ale są jednak bardzo różne podejścia.
Aaronaught
2
Co powiesz na analogię: modyfikator A private/ protectedaccess przypomina prezerwatywę. Nie musisz go używać, ale jeśli tego nie zrobisz, lepiej bądź cholernie pewien swojego czasu i partnera (-ów). Nie zapobiegnie to złośliwemu działaniu i może nawet nie zadziałać za każdym razem, ale zdecydowanie obniży ryzyko związane z niedbalstwem i zrobi to o wiele bardziej skutecznie niż powiedzenie „uważaj”. Aha, a jeśli masz, ale nie używaj go, nie oczekuj ode mnie żadnej sympatii.
Aaronaught,
3

Ukrywanie informacji to znacznie więcej niż tylko konwencja; próba obejścia tego może w wielu przypadkach zakłócić funkcjonalność klasy. Na przykład, dość powszechną praktyką jest przechowywanie wartości w privatezmiennej, ujawnianie jej za pomocą właściwości protectedlub publicw tym samym czasie, aw module pobierającym sprawdzanie wartości null i wykonywanie wszelkich niezbędnych inicjalizacji (tj. Opóźnionego ładowania). Lub przechowuj coś w privatezmiennej, ujawniaj to za pomocą właściwości, a w seterze sprawdź, czy wartość się zmieniła i odpal PropertyChanging/ PropertyChangedzdarzenia. Ale bez zobaczenia wewnętrznej implementacji nigdy nie dowiesz się wszystkiego, co dzieje się za kulisami.

Joel C.
źródło
3

Ukrywanie informacji wywodzi się z odgórnej filozofii projektowania. Python został nazwany językiem oddolnym .

Ukrywanie informacji jest dobrze egzekwowane na poziomie klas w Javie, C ++ i C #, więc tak naprawdę nie jest to konwencja na tym poziomie. Bardzo łatwo jest uczynić klasę „czarną skrzynką” z publicznymi interfejsami i ukrytymi (prywatnymi) szczegółami.

Jak już wspomniałeś, w Pythonie programiści muszą przestrzegać konwencji o niestosowaniu tego, co ma być ukryte, ponieważ wszystko jest widoczne.

Nawet w Javie, C ++ lub C # w pewnym momencie ukrywanie informacji staje się konwencją. Nie ma kontroli dostępu na najwyższych poziomach abstrakcji zaangażowanych w bardziej złożone architektury oprogramowania. Na przykład w Javie można znaleźć użycie „.internal”. nazwy pakietów. Jest to czysto konwencja nazewnictwa, ponieważ nie jest łatwo wymusić tego rodzaju ukrywanie informacji za pomocą samej dostępności pakietów.

Jednym z języków, który dąży do formalnego zdefiniowania dostępu, jest Eiffel. W tym artykule wskazano na inne słabości kryjące informacje w językach takich jak Java.

Tło: Ukrywanie informacji zaproponował David Parnas w 1971 roku . Wskazuje w tym artykule, że wykorzystanie informacji o innych modułach może „katastrofalnie zwiększyć łączność struktury systemu”. Zgodnie z tym pomysłem brak ukrywania informacji może prowadzić do ciasno powiązanych systemów, które są trudne w utrzymaniu. Kontynuuje:

Chcemy, aby struktura systemu została wyraźnie określona przez projektantów przed rozpoczęciem programowania, a nie przypadkowo przez wykorzystanie informacji przez programistę.

Fuhrmanator
źródło
1
+1 za cytat „wykorzystanie informacji przez programistę”, to jest dokładnie to (chociaż na końcu masz dodatkowy przecinek).
jmoreno
2

Ruby i PHP mają to i wymuszają w czasie wykonywania.

Punktem ukrywania informacji jest tak naprawdę pokazywanie informacji. Poprzez „ukrywanie” wewnętrznych szczegółów cel staje się widoczny z zewnętrznej perspektywy. Istnieją języki, które to obejmują. W Javie domyślnym dostępem jest pakiet wewnętrzny, w haX chroniony. Wyraźnie deklarujesz je jako publiczne, aby je ujawnić.

Chodzi o to, aby uczynić lekcje łatwymi w użyciu, ujawniając tylko bardzo spójny interfejs. Chcesz, aby reszta była chroniona, aby żaden sprytny facet nie przychodził i nie bałaganił twojego wewnętrznego stanu, aby oszukać klasę, by robiła to, co chce.

Również, gdy modyfikatory dostępu są egzekwowane na starcie, to można ich użyć do wymuszenia określonego poziomu bezpieczeństwa, ale nie sądzę, że jest to szczególnie dobre rozwiązanie.

back2dos
źródło
1
Jeśli Twoja biblioteka jest używana w tej samej firmie, w której pracujesz, będziesz się przejmować ... Jeśli zepsuje swoją aplikację, będzie ci trudno ją naprawić.
wleao,
1
Po co udawać, że nosi się pas bezpieczeństwa, skoro można go nosić? Przeredaguję pytanie i zapytam, czy jest jakiś powód, aby kompilator go nie egzekwował, jeśli wszystkie informacje są dla niego dostępne. Nie chcesz czekać, aż do wypadku, aby dowiedzieć się, czy warto było zapiąć pas bezpieczeństwa.
Mark H
1
@ Simon: dlaczego zaśmiecać interfejs z rzeczami, których nie chcesz, aby ludzie używali. Nienawidzę tego, że C ++ wymaga obecności każdego członka w definicji klasy w pliku nagłówkowym (rozumiem dlaczego). Interfejsy są dla innych ludzi i nie powinny być zaśmiecone rzeczami, których nie mogą używać.
phkahler,
1
@phkahler: pydocusuwa _prefixedMembersz interfejsu. Zaśmiecone interfejsy nie mają nic wspólnego z wyborem słowa kluczowego sprawdzanego przez kompilator.
blubb
2

Modyfikatory dostępu mogą z pewnością zrobić coś, czego konwencja nie może.

Na przykład w Javie nie możesz uzyskać dostępu do prywatnego członka / pola, chyba że użyjesz refleksji.

Dlatego jeśli napiszę interfejs dla wtyczki i poprawnie odmówię prawa do modyfikowania pól prywatnych poprzez odbicie (i ustawienia menedżera bezpieczeństwa :)), mogę wysłać jakiś obiekt do funkcji zaimplementowanych przez kogokolwiek i wiem, że nie może on uzyskać dostępu do jego pól prywatnych.

Oczywiście mogą istnieć pewne błędy bezpieczeństwa, które pozwolą mu to przezwyciężyć, ale nie jest to filozoficznie ważne (ale w praktyce tak jest zdecydowanie).

Jeśli użytkownik uruchamia mój interfejs w swoim środowisku, ma kontrolę, a tym samym może obchodzić modyfikatory dostępu.

użytkownik470365
źródło
2

Nie byłem w sytuacji, w której błąd kompilatora spowodowany tymi słowami kluczowymi uratowałby mnie przed błędem.

Nie tyle zapisywanie autorów aplikacji przed błędami, co pozwalanie autorom bibliotek decydować, które części ich implementacji zobowiązują się utrzymywać.

Jeśli mam bibliotekę

class C {
  public void foo() { ... }

  private void fooHelper() { /* lots of complex code */ }
}

Mogę chcieć zastąpić foowdrożenie i ewentualnie zmienić się fooHelperradykalnie. Jeśli grupa osób zdecydowała się użyć fooHelperpomimo wszystkich ostrzeżeń w mojej dokumentacji, być może nie będę w stanie tego zrobić.

privatepozwala autorom bibliotek rozkładać biblioteki na możliwe do zarządzania metody (i privateklasy pomocnicze) bez obawy, że będą zmuszeni do zachowania tych wewnętrznych szczegółów przez lata.


Co byłoby warte wymuszenia ukrywania informacji przez kompilator?

Na marginesie, w Javie privatenie jest egzekwowany przez kompilator, ale przez weryfikator kodu bajtowego Java .


Czy odbicie może zastąpić ten mechanizm? Co byłoby warte wymuszenia ukrywania informacji przez kompilator?

W Javie nie tylko odbicie może zastąpić ten mechanizm. Istnieją dwa rodzaje privateJava. Tego rodzaju privateuniemożliwia jednej klasie zewnętrznej dostęp do elementów innej klasy zewnętrznej, privateco jest sprawdzane przez weryfikator kodu bajtowego, ale także tych, privatektóre są używane przez klasę wewnętrzną za pomocą metody prywatnego syntetycznego akcesorium pakietu jak w

 public class C {
   private int i = 42;

   public class B {
     public void incr() { ++i; }
   }
 }

Ponieważ klasa B(naprawdę nazwana C$B) używa i, kompilator tworzy syntetyczną metodę akcesora, która pozwala Bna dostęp C.iw sposób, który mija weryfikator kodu bajtowego. Niestety, ponieważ ClassLoaderpozwala ci stworzyć klasę z byte[], dość łatwo jest dostać się do szeregowców, którzy Cwystawili się na klasy wewnętrzne, tworząc nową klasę w Cpakiecie, co jest możliwe, jeśli Csłoik nie został zapieczętowany.

Właściwe privateegzekwowanie wymaga koordynacji między modułami ładującymi, weryfikatorem kodu bajtowego i polityką bezpieczeństwa, która może uniemożliwić odbijający dostęp do szeregowych.


Czy można go wykorzystać do zabezpieczenia tajnego stanu klasy przed atakiem?

Tak. „Bezpieczny rozkład” jest możliwy, gdy programiści mogą współpracować, zachowując jednocześnie właściwości bezpieczeństwa swoich modułów - nie muszę ufać autorowi innego modułu kodu, aby nie naruszył właściwości bezpieczeństwa mojego modułu.

Języki Object Objectabilities, takie jak Joe-E, wykorzystują ukrywanie informacji i inne środki umożliwiające bezpieczny rozkład:

Joe-E jest podzbiorem języka programowania Java zaprojektowanym do obsługi bezpiecznego programowania zgodnie z dyscypliną możliwości obiektowych. Joe-E ma na celu ułatwienie budowy bezpiecznych systemów, a także ułatwienie przeglądów bezpieczeństwa systemów zbudowanych w Joe-E.

Dokument połączony z tej strony podaje przykład, w jaki sposób privateegzekwowanie umożliwia bezpieczny rozkład.

Zapewnia bezpieczne enkapsulację.

Rysunek 1. Funkcja rejestrowania tylko z dołączaniem.

public final class Log {
  private final StringBuilder content;
  public Log() {
    content = new StringBuilder();
  }
  public void write(String s) {
    content.append(s);
  }
}

Rozważmy ryc. 1, która ilustruje, w jaki sposób można zbudować narzędzie do rejestrowania tylko z dołączaniem. Pod warunkiem, że reszta programu jest napisana w Joe-E, recenzent kodu może mieć pewność, że wpisy dziennika można tylko dodawać, a nie można ich modyfikować ani usuwać. Ta recenzja jest praktyczna, ponieważ wymaga jedynie kontroli Log klasy i nie wymaga przeglądu żadnego innego kodu. W związku z tym weryfikacja tej właściwości wymaga tylko lokalnego uzasadnienia dotyczącego kodu rejestrowania.

Mike Samuel
źródło
1

Ukrywanie informacji jest jednym z głównych problemów dobrego projektowania oprogramowania. Sprawdź którykolwiek z artykułów Dave'a Parnasa z późnych lat 70. Zasadniczo, jeśli nie możesz zagwarantować, że stan wewnętrzny modułu jest spójny, nie możesz zagwarantować niczego na temat jego zachowania. Jedynym sposobem, w jaki możesz zagwarantować jego stan wewnętrzny, jest utrzymanie go w tajemnicy i zezwolenie na jego zmianę tylko za pomocą podanych przez ciebie środków.

TMN
źródło
1

Uczyniając ochronę częścią języka, zyskujesz coś: rozsądną pewność.

Jeśli zmienię zmienną na prywatną, mam uzasadnioną pewność , że zostanie zmieniona tylko przez kod w tej klasie lub jawnie zadeklarowanych przyjaciół tej klasy. Zakres kodu, który mógłby rozsądnie dotykać tej wartości, jest ograniczony i wyraźnie zdefiniowany.

Czy są teraz sposoby obejścia ochrony składniowej? Absolutnie; większość języków je ma. W C ++ zawsze możesz rzucić klasę na inny typ i szturchać jego bity. W Javie i C # możesz się w to odzwierciedlić. I tak dalej.

Jest to jednak trudne . To oczywiste, że robisz coś, czego nie powinieneś robić. Nie możesz tego zrobić przypadkowo (poza dzikimi zapisami w C ++). Musisz chętnie pomyśleć: „Dotknę czegoś, czego mój kompilator nie powiedział mi”. Musisz dobrowolnie zrobić coś nierozsądnego .

Bez ochrony składniowej programista może po prostu przypadkowo coś zepsuć. Musisz nauczyć użytkownika konwencji, a on musi przestrzegać tej konwencji za każdym razem . Jeśli tego nie zrobią, świat stanie się bardzo niebezpieczny.

Bez ochrony składniowej ciężar leży po stronie niewłaściwych ludzi: wielu ludzi korzystających z klasy. Muszą postępować zgodnie z konwencją, w przeciwnym razie nastąpi nieokreślona zła. Zadrapanie, że: może wystąpić nieokreślona zła .

Nie ma nic gorszego niż interfejs API, w którym jeśli zrobisz coś złego, wszystko i tak może działać. Zapewnia to fałszywe zapewnienie użytkownikowi, że zrobił właściwą rzecz, że wszystko jest w porządku itp.

A co z nowymi użytkownikami tego języka? Muszą nie tylko nauczyć się i przestrzegać faktycznej składni (wymuszonej przez kompilator), ale teraz muszą przestrzegać tej konwencji (wymuszonej przez swoich rówieśników w najlepszym wypadku). A jeśli nie, to nic złego się nie stanie. Co się stanie, jeśli programista nie zrozumie, dlaczego konwencja istnieje? Co się stanie, gdy powie „pieprzyć to” i po prostu wyśmiewa twoich szeregowych? A co on myśli, jeśli wszystko nadal działa?

Uważa, że ​​konwencja jest głupia. I nigdy więcej go nie podąży. I powie wszystkim swoim przyjaciołom, żeby nie zawracali sobie głowy.

Nicol Bolas
źródło