Корректность и устойчивость программных систем. Простая форма с проверкой

Этот раздел посвящен описанию промежуточного подхода. Основные практические решения изложены в лекции 17 .

Изучая вариант с закреплением, мы заметили, что его основной идеей было разделение ковариантного и полиморфного наборов сущностей. Так, если взять две инструкции вида

каждая из них служит примером правильного применения важных ОО-механизмов: первая - полиморфизма, вторая - переопределения типов. Проблемы начинаются при объединении их для одной и той же сущностиs . Аналогично:

p.add_vertex (...)

проблемы начинаются с объединения двух независимых и совершенно невинных операторов.

Ошибочные вызовы ведут к нарушению типов. В первом примере полиморфное присваивание присоединяет объект BOY к сущностиs , что делаетg недопустимым аргументомshare , так как она связана с объектомGIRL . Во втором примере к сущностиr присоединяется объектRECTANGLE , что исключаетadd_vertex из числа экспортируемых компонентов.

Вот и идея нового решения: заранее - статически, при проверке типов компилятором или иными инструментальными средствами - определим набор типов (typeset) каждой сущности, включающий типы объектов, с которыми сущность может быть связана в период выполнения. Затем, опять же статически, мы убедимся в том, что каждый вызов является правильным для каждого элемента из наборов типов цели и аргументов.

В наших примерах оператор s:= b указывает на то, что классBOY принадлежит набору типов дляs (поскольку в результате выполнения инструкции созданияcreate b он принадлежит набору типов дляb ).GIRL , ввиду наличия инструкцииcreate g , принадлежит набору типов дляg . Но тогда вызовshare будет недопустим для целиs типаBOY и аргументаg типаGIRL . АналогичноRECTANGLE находится в наборе типов дляp , что обусловлено полиморфным присваиванием, однако, вызовadd_vertex дляp типаRECTANGLE окажется недопустимым.

Эти наблюдения наводят нас на мысль о создании глобального подхода на основе нового правила типизации:

Правило системной корректности

Вызов x.f (arg) является системно-корректным, если и только если он классово-корректен дляx , иarg , имеющих любые типы из своих соответствующих наборов типов.

В этом определении вызов считается классово-корректным, если он не нарушает правила Вызова Компонентов, которое гласит: еслиC есть базовый класс типаx , компонентf должен экспортироватьсяC , а типarg должен быть совместим с типом формального параметраf . (Вспомните: для простоты мы полагаем, что каждый подпрограмма имеет только один параметр, однако, не составляет труда расширить действие правила на произвольное число аргументов.)

Системная корректность вызова сводится к классовой корректности за тем исключением, что она проверяется не для отдельных элементов, а для любых пар из наборов множеств. Вот

основные правила создания набора типов для каждой сущности: 1 Для каждой сущности начальный набор типов пуст.

2 Встретив очередную инструкцию видаcreate {SOME_TYPE} a , добавимSOME_TYPE в набор типов дляa . (Для простоты будем полагать, что любая инструкцияcreate a будет заменена инструкциейcreate {ATYPE} a , гдеATYPE - тип сущностиa .)

3 Встретив очередное присваивание видаa:= b , добавим в набор типов дляa все элементы набора типов дляb .

4 Еслиa есть формальный параметр подпрограммы, то, встретив очередной вызов с фактическим параметромb , добавим в набор типов дляa все элементы набора типов дляb .

5 Будем повторять шаги (3) и (4) до тех пор, пока наборы типов не перестанут изменяться. Данная формулировка не учитывает механизма универсальности, однако расширить

правило нужным образом можно без особых проблем. Шаг (5) необходим ввиду возможности цепочек присваивания и передач (от b кa , отc кb и т. д.). Нетрудно понять, что через конечное число шагов этот процесс прекратится.

|Число шагов ограничено длиной максимальной цепочки присоединений; другими словами максимум равен n , если система содержит присоединения отx i+1 кx i д л яi=1, 2, ... n-1 . Повторение шагов (3) и (4) известно как метод "неподвижной точки". |

Как вы, возможно, заметили, правило не учитывает последовательности инструкций. В случае

create {TYPE1} t; s:= t; create {TYPE2} t

в набор типов для s войдет какTYPE1 , так иTYPE2 , хотяs , учитывая последовательность инструкций, способен принимать значения только первого типа. Учет расположения инструкций потребует от компилятора глубокого анализа потока команд, что приведет к чрезмерному повышению уровня сложности алгоритма. Вместо этого применяются более пессимистичные правила: последовательность операций:

будет объявлена системно-некорректной, несмотря на то, что последовательность их выполнения не приводит к нарушению типа.

Глобальный анализ системы был (более детально) представлен в 22-й главе монографии . При этом была решена как проблема ковариантности, так и проблема ограничений экспорта при наследовании. Однако в этом подходе есть досадный практический недочет, а именно: предполагается проверкасистемы в целом , а не каждого класса в отдельности. Убийственным оказывается правило (4), которое при вызове библиотечной подпрограммы будет учитывать все ее возможные вызовы в других классах.

Хотя затем были предложены алгоритмы работы с отдельными классами в , их практическую ценность установить не удалось. Это означало, что в среде программирования, поддерживающей возрастающую компиляцию, необходимо будет организовать проверку всей системы. Желательно проверку вводить как элемент (быстрой) локальной обработки изменений, внесенных пользователем в некоторые классы. Хотя примеры применения глобального подхода

известны, - так, программисты на языке C используют инструмент lint для поиска несоответствий в системе, не обнаруживаемых компилятором, - все это выглядит не слишком привлекательно.

В итоге, как мне известно, проверка системной корректности осталась никем не реализованной. (Другой причиной такого исхода, возможно, послужила сложность самих правил проверки.)

Классовая корректность предполагает проверку, ограниченную классом, и, следовательно, возможна при возрастающей компиляции. Системная корректность предполагает глобальную проверку всей системы, что входит в противоречие с возрастающей компиляцией.

Однако, несмотря на свое имя, фактически можно проверить системную корректность, используя только возрастающую проверку классов (в процессе работы обычного компилятора). Это и будет финальным вкладом в решение проблемы.


В позапрошлую пятницу я поучаствовал в одном интересном обсуждении. В общих чертах, речь шла о том, как нужно и как не нужно проверять параметры функции. В частности же, были окончательно затоптаны и преданы анафеме бренные останки функций IsBadReadPtr и IsBadWritePtr.

Если вы читали «Should I check the parameters to my function? » в блоге Larry Osterman, и являетесь сторонником второго подхода – можете дальше не читать. :-)

Когда то давно, функции IsBadReadPtr и IsBadWritePtr использовались для проверки валидности переданных указателей. Правда, со временем выяснилось, что от этих функций больше вреда, чем пользы. Во-первых, вместо обнаружения некорректных указателей (что по идее было целью их создателей), эти функции скорее скрывали ошибки. Во-вторых, даже успешное тестирование указателя с помощью одной из функций не гарантировало успешность следующей операции с этой памятью в многозадачной среде. В-третьих, IsBadWritePtr портит память по фактически случайному адресу – гарантия того, что приложение всё-таки упадёт позднее, но только с большими неприятностями. В-четвёртых, даже IsBadReadPtr может быть виновником в сбое приложения, если проверяемый адрес приходится на зашитную страницу (guard page) стека. В результате система потеряет возможность увеличивать стек, по мере надобности, и приложение с грохотом упадёт, если попытается это сделать.

Как же правильно проверять переданные указатели? Правило простое – любое значение указателя не равное 0 считается корректным. Если указатель на самом деле указывает «не туда», то приложение завершиться ошибкой доступа, что, в общем-то, плохо, но помогает найти и исправить ошибки быстрее.

Это правило не работает для специальных случаев, вроде точки входа в ядре операционной системы или RPC. Хотя в случае RPC каждая из сторон сама контролирует выделение буферов, так что функциональность IsBadXxxPtr все равно не требуется.

В случае ядерного вызова указатели должны проверяются на корректность, однако и в этом случае IsBadXxxPtr оказывается не у дел. NT ядро использует функции ProbeForRead и ProbeForWrite , которые во много похожи на IsBadXxxPtr. Не смотря на похожесть, ProbeForXxx главным образом проверяют, что переданный буфер целиком находится в пользовательском адресном пространстве, а не то, что буфер размещён в валидной памяти. Дальнейшие обращения к переданному буферу в любом случае окружаются блоком __try - __except. Иными словами такая проверка гарантирует, что пользовательский код не сможет заставить ядерный код обратится к какому-либо адресу в адресном пространстве ядра. Тем самым ситуация с порчей защитной страницы ядерного стека, как результат запроса из пользовательского кода, не возможна. Само собой, что защитная страница пользовательского стека по-прежнему может пострадать. Но тут уж само приложение виновато.

Все основные функции движка уже готовы. Во всяком случае, те, что отвечают за выборку контента и навигацию. Осталась одна деликатная тема: что делать, если кто-то запросил некорректный URL?

Статические сайты обслуживаются сервером - если запрошенный файл не найден, сервер выдаст заголовок (HTTP Response Header) с кодом статуса «404 Not Found». Но в случае динамического движка отдаваемый документ определяется не по имени файла в URL, а по переданным скрипту параметрам. При неверных параметрах файл скрипта все равно лежит на своем месте, значит сервер запустит его и отдаст код статуса «200 OK». А документа, соответствующего параметрам, нет. Поэтому скрипт должен определить некорректные парамеры и переписать код статуса до начала выдачи документа. И прекратить работу, не пытаясь искать несуществующие данные.

Для такого аварийного завершения нам потребуются всего две вещи. Первая - HTML-файл страницы с сообщением об ошибке - для выдачи в браузер пользователя. Приготовим его заранее и положим в корневую директорию сайта под именем 404.htm (так он у нас прописан в файле конфигурации).
Вторая - функция, которая отсылает HTTP-заголовок с кодом ошибки и прекращает работу скрипта. Выглядит она так:

Глобальные переменные $charset и $page404 определены в файле конфигурации. А параметр $pageout - логическая переменная, определяющая, выдавать страницу с сообщением об ошибке или нет. По умолчанию его значение FALSE - то есть функция только отдаст HTTP-заголовок с кодом статуса и прекратит работу. Версия протокола HTTP берется из переменной окружения SERVER_PROTOCOL, чтобы послать отклик именно с той версией, по которой работает сервер. Если функцию запустить с параметром TRUE, то после передачи заголовка она считает и отправит в поток вывода файл со страницей сообщения. Выдача только заголовка без сообщения может пригодиться в дальнейшем - в тех случаях, когда точно известно, что запрос послан не браузером, а роботом.

В директории /libs создаем файл functn.inc.php - в него помещаем функцию err404() и функцию hierarchy(), описанную в разделе Разбор структуры сайта . В тот же файл записываем функции выборки разделов для меню: getmain(), getsub() и getlevel(), описанные в разделе Выборка разделов для меню . Базовая библиотека готова. Теперь самое время провести проверку корректности запроса. Этот код уже пойдет в начало центрального скрипта main.php

В первых двух строках подгружается файл конфигурации и файл с функциями. Затем определяем логическую переменную $fakelink - она работает «флажком», который будет поднят при любом некорректном состоянии URL. Далее начинаем серию проверок.

Первое условие проводит проверку в случае, когда включен режим трансляции статических URL (переменная $urlmode не пустая и не равна "none"). Из переменной окружения REQUEST_URI берется первоначально запрошенный (не транслированный) URL и проверяется на отсутствие вопросительного знака. Если ссылки статические, его там быть не должно - статические URL параметров не имеют.

Второе условие:

  • при наличии GET-параметров (в случае статических ссылок они появляются после трансляции URL) перебирается в цикле массив $_GET. В ходе перебора наименование каждого параметра проверяется на наличие такого имени в массиве разрешенных (массив определен в файле конфигурации). Неизвестное имя параметра вызывает взвод флажка $fakelink в состояние TRUE и выход из цикла. Попутно создаются переменные с теми же именами и теми же значениями, что и у параметров. Возможные пробельные символы в значениях отсекаются.
  • при отсутствии GET-параметров считаем, что было обращение к корню сайта. Проверяется переменная окружения QUERY_STRING - она в этом случае должна быть пустой. Но туда могут попасть кое-какие нелепые довески к запросу корня, не попадающие в $_GET - наподобие http://domain.ru/?=

После первых двух проверок нужно остановить работу скрипта, если запрошенный URL некорректен. Что мы и делаем - при поднятом флажке $fakelink запускается передача статуса «404 Not Found» с завершением работы скрипта.

Далее, если URL корректного формата, считывается структура сайта и формируется массив разделов. И тут же следует третья проверка - если в массиве разделов не обнаружен алиас запрошенного раздела, значит пришел запрос с корректным форматом, но запрошенная страница не существует. В этом случае также выдается статус 404 и скрипт завершает работу.

Если все проверки пройдены благополучно и запрошенный раздел существует, можно наконец выбирать контент, формировать меню и собирать страницу.

Данный урок описывает, как создать JavaScript форму, которая проверяет правильность заполнения посетителем полей перед отправкой данных на сервер. Сначала мы объясним, почему проверка заполнения формы является полезной методикой, а затем построим простой пример с объяснением, как все происходит.

Зачем нужна проверка заполнения формы?

Проверка заполнения формы - процесс, при котором данные формы проверяются перед обработкой. Например, если Ваша форма имеет поле, в которое пользователь должен ввести email адрес, возможно Вы захотите проверить, что поле заполнено, прежде чем начать дальнейшую обработку формы.

Существует два основных метода для проверки заполнения формы: на стороне сервера (с использованием CGI скриптов, ASP и т.д.) и на стороне клиента (обычно используется JavaScript). Проверка на стороне сервера более безопасная, но в большинстве случаев требует более сложного кода, в то время как проверка на стороне клиента выполняется проще и быстрее (браузер не нуждается в соединении с сервером для проверки заполнения формы, таким образом, пользователь получает немедленный ответ в случае пропущенных полей, которые необходимо заполнить).

Проверка формы на стороне клиента. Обычно выполняется с помощью встроенного JavaScript скрипта.

Проверка формы на стороне сервера. Обычно выполняется с помощью CGI или ASP скрипта.

В данном уроке мы построим простую форму с проверкой на стороне клиента с помощью JavaScript. Вы сможете затем адаптировать ее под свои нужды.

Простая форма с проверкой.

Давайте построим простую форму с проверкой заполнения с помощью скрипта. Данная форма имеет одно текстовое поле "Ваше имя" и кнопку для отправки данных. Наш скрипт проверяет, что пользователь ввел свое имя перед тем, как отправить данные на сервер.

Откройте форму и посмотрите в действии. Попробуйте нажать кнопку "Отправить данные" ничего не вводя в поле "Ваше имя".

Страница содержит функцию JavaScript, которая называется validate_form(). Она выполняет проверку заполнения формы. давайте посмотрим сначала на форму.

Форма

Первая часть формы - тэг form

Форма имеет имя contact_form. С его помощью мы получим доступ к форме из JavaScript функции проверки.

Форма использует постметод для отправки данных в htm-файл заглушку, который просто выводит сообщение. В действительности Вы можете пересылать данные Вашему CGI скрипту, ASP странице и т.д. (например для отправки почты).

Также тэг form содержит onsubmit атрибут для вызова JavaScript функции проверки validate_form (), когда нажимается кнопка "Отправить данные". Функция возвращает логическое значение, для которого true означает "проверка прошла успешно", а false - "данные задержаны". Таким образом мы можем предотвратить отправку данных формы, если пользователь не заполнил ее правильно.

Остальной код формы добавляет поле ввода contact_name и кнопку "Отправить данные":

Пожалуйста введите Ваше имя.

Ваше имя:

Функция validate_form()

Функция проверки формы validate_form() встроена в секцию head вначале страницы:

Первая строчка () указывает браузеру, что далее идет код JavaScript, а коментарий HTML (


Top