![]() |
![]() |
![]()
Post
#1
|
|
Grupa: Zarejestrowani Postów: 53 Pomógł: 1 Dołączył: 28.09.2007 Skąd: Gdynia Ostrzeżenie: (0%) ![]() ![]() |
Początkowo zadałem pytanie jak rozwiązać problem, i w jakimś amoku sam go rozwiązałem.
Problem jest stary jak PHP. Powiedzmy, że chcemy wykonać wygenerowany kod via eval(), ale nie mamy pewności, że nie spowoduje on błędu fatalnego - kładąc główny kod. Po co? Ja na przykład zrobiłem kompilator własnego języka. Nie będę się już wdawał w szczeóły po co. Był potrzebny i już. Klient zamówił - klient dostał. Problem jednak w tym, że klient skrypty w tym nowym języku musi dość często aktualizować. W dodatku klient nie jest programistą. Dlatego język skryptu maksymalnie przypomina naturalny język polski. Nie udało się jednak uciec przed jakąś podstawową składnią, w końcu to też język programowania, który w dodatku robi dość skomplikowane operacje na danych. Jeśli w skrypcie użytkownika pojawi się błąd, którego nie wykryje mój kompilator - powstanie błędny kod wynikowy, który wysypie kod główny. A to się po prostu nie ma prawa zdarzyć. Klient musi otrzymać informację, że ma w skrypcie błąd, dostać jakąś podpowiedź w postaci np fragmentu kodu, którego nie udało się wykonać. Ważne, żeby dostał informację KTÓRY z kilkunastu skryptów się wysypał. Pozostałe MUSZĄ się wykonać. Używając eval() - niewykonalne. Prosty test: @eval('return test::test();'); Jeśli nie ma klasy test - kod się sypnie. Nic po eval() się już nie wykona. Bo jak czytałem na StackOverflow - błędy fatalne są fatalne. Dobra, to teraz rozwiązanie, czyli SUPER EVAL (IMG:style_emoticons/default/smile.gif) Uwaga: żeby nie było, poprawiłem tą funkcję - dodałem escapeshellarg, inaczej kod by się wysypał od pojedynczego quota. Funkcja zachowuje się jak zwykły eval() z jednym wyjątkiem: w przypadku błędu fatalnego zwraca komunikat o błędzie jako string. Oczywiście można ją przerobić, żeby zwracała informację o tym, czy wystąpił błąd, i wartość wyjściową, to już kwestia trywialna. Oczywiście pewnie jest to wolniejsze od zwykłego evala, ale jest jedynym sposobem na uruchomienie kodu mogącego zawierać błąd. Co istotne, w razie błędu zwracany jest właściwy numer linii w której wystąpił. Nie ma żadnych ograniczeń jeśli chodzi o używanie include, require i klas wewnątrz kodu. Przyda się komuś? Ten post edytował pp-layouts 14.01.2010, 12:57:46 |
|
|
![]() |
![]()
Post
#2
|
|
Grupa: Zarejestrowani Postów: 53 Pomógł: 1 Dołączył: 28.09.2007 Skąd: Gdynia Ostrzeżenie: (0%) ![]() ![]() |
Ta aplikacja nie jest dostępna publicznie, tylko dla pracowników firmy. Mogą tylko sobie kuku zrobić (IMG:style_emoticons/default/smile.gif) Swoją drogą miałem jakiegoś buga w kompilatorze, wróciłem go do nieco starszej wersji (w której nie zdążyłem jeszcze namieszać po zarwanej nocce) - i nie jestem w stanie napisać skryptu który wywołałby fatala w kodzie wynikowym (IMG:style_emoticons/default/smile.gif) Wszystkie celowo umieszczane bugi wyłapywał albo kompilator, albo parsekit. Tak czy siak, mojego super evala można potraktować jako ciekawostkę i pewien sposób rozszerzenia standardowej funkcjonalności PHP.
Co do robienia kuku, ciekawi mnie jak, przy maksimum złej woli nabroić cokolwiek, jeśli cały kod użytkownika jest kompilowany, czyli jedyne co z niego przechodzi w postaci niezmienionej są stałe. Nie będę zdradzał tutaj całego algorytmu, ale jedynym sposobem na zepsucie czegoś to zepsucie escapowania stringów, żeby kompilator wrzucił cytat jako kod, ale to się DA zabezpieczyć, szczególnie że format skryptu wejściowego jest raczej dość ścisły. Jest tylko jeden rodzaj cytowania (single quote), nie ma możliwości bezpośredniego escapowania w literalach. Jedyny sposób żeby ten znak umieścić w danych wejściowych to użycie wstawki CSV (jest częścią składni mojego języka). Każde inne użycie single quota spowoduje, że kompilator odrzuci kod i zwróci syntax error. A CSV jest zawsze wstawiany jako dane, nie ma możliwości żeby cokolwiek przeszło do kodu. Wiem, zdolny hacker złamie wszystko, no i właśnie, nie ma co się szczypać - mój eval nie jest mniej bezpieczny od całej reszty kodu czy samego PHP.
Finalna, przetestowana wersja tej funkcji. Wszystkie błędy fatalne i nieobsłużone wyjątki wykonywanego kodu zostaną przekazane jako wyjątek, który można, a nawet trzeba obsłużyć w bloku try / catch. Przy okazji warto zauważyć kilka rzeczy, których odkrycie kosztowało mnie kilka zarwanych nocy: maksymalny rozmiar wykonywanego kodu to ca 64kb lub mniej, zależnie od shella, co jest spowodowane maksymalną długością przetwarzanego polecenia. Obejściem tego limitu jest użycie include / require w wykonywanym kodzie. Kolejną różnicą od zwykłego evala, co jest raczej oczywiste - s_eval działa w czystym środowisku, wszelkie wymagania w postaci bibliotek muszą być dołączone jeszcze raz. Przy okazji wyszedł też bug w samym PHP - w shellu nie działa polecenie set_exception_handler(); Obszedłem to poprzez użycie bloku try / catch i przekazanie obiektu wyjątku. W praktycznym zastosowaniu uruchomienie kodu przebiega w 2 etapach. Pierwszy używa s_eval() i sprawdza, czy kod jest poprawny (względnie loguje wszystkie błędy i wyjątki). Jeśli nie wykryto błędu, kod zapisywany jest w pliku cache i przy kolejnych wywołaniach jest wykonywany zwykłym evalem, co oszczędza ponad 90% czasu. Każde wykrycie błędu w kodzie powoduje inwalidację cache (data pliku cache jest cofana względem daty źródła). Metoda ta jest także szybsza od użycia parsekita przy każdym uruchomieniu kodu użytkownika (około 8x). Konieczność wykonania kodu użytkownika wbrew temu, co większość z Was pisze może wystąpić i nie musi to oznaczać wcale błędnych założeń. Eval był i jest ogólnie odradzany przez m. in. olbrzymie trudności w debugowaniu kodu. Z moją funkcją obsługującą wyjątki debugowanie jest prostsze, ba - w ogóle jest możliwe. Może oszczędzę komuś wielu godzin frustrującego rozgryzania tematu. |
|
|
![]() ![]() |
![]() |
Aktualny czas: 15.10.2025 - 02:12 |