Из Википедии, бесплатной энциклопедии
Перейти к навигации Перейти к поиску

В информатике , безопасность типа является степенью , в которой язык программирование отпугивает или предотвращают ошибки типа . Ошибка типа - это ошибочное или нежелательное поведение программы, вызванное несоответствием между разными типами данных для констант, переменных и методов (функций) программы, например, обработка целого числа ( int ) как числа с плавающей запятой ( float ). Типовая безопасность иногда альтернативно рассматривается как свойство компьютерной программы, а не как язык, на котором эта программа написана; то есть в некоторых языках есть средства обеспечения безопасности типов, которые могут быть обойдены программистами, применяющими методы, демонстрирующие низкую безопасность типов. ФормальныйТеоретико-типовое определение безопасности типов значительно сильнее, чем то, что понимает большинство программистов.

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

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

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

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

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

Определения [ править ]

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

Робин Милнер использовал следующий слоган для описания безопасности типов:

Хорошо напечатанные программы не могут «ошибиться». [1]

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

В 1994 году Эндрю Райт и Маттиас Фелляйзен сформулировали то, что сейчас является стандартным методом определения и доказательства безопасности типов в языках, определяемых операционной семантикой . При таком подходе безопасность типов определяется двумя свойствами семантики языка программирования:

(Тип-) сохранение или уменьшение предмета
«Хорошо типизированная» («типируемость») программ остается неизменной в соответствии с правилами перехода (т. Е. Правилами оценки или правилами сокращения) языка.
Прогресс
Хорошо типизированная (типизированная) программа никогда не «застревает», что означает, что выражения в программе будут либо оцениваться как значение , либо для этого существует правило перехода; другими словами, программа никогда не переходит в неопределенное состояние, в котором дальнейшие переходы невозможны.

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

Виджай Сарасват дает следующее определение:

«Язык является типобезопасным, если с данными на языке могут выполняться только операции, санкционированные типом данных». [2]

Отношение к другим формам безопасности [ править ]

Типовая безопасность в конечном итоге направлена ​​на исключение других проблем, например:

  • Предупреждение незаконных операций. Например, мы можем идентифицировать выражение 3 / "Hello, World"как недопустимое, потому что правила арифметики не определяют, как разделить целое число на строку .
  • Безопасность памяти
    • Дикие указатели могут возникать, когда указатель на объект одного типа рассматривается как указатель на другой тип. Например, размер объекта зависит от типа, поэтому, если указатель увеличивается под неправильными учетными данными, он будет указывать на некоторую случайную область памяти.
    • Переполнение буфера. Запись вне пределов может привести к повреждению содержимого объектов, уже присутствующих в куче. Это может произойти, когда более крупный объект одного типа грубо копируется в более мелкий объект другого типа.
  • Логические ошибки, возникающие в семантике разного типа. Например, дюймы и миллиметры могут храниться как целые числа, но не должны заменяться друг другом или складываться. Система типов может применять для них два разных типа целых чисел.

Типобезопасные и небезопасные языки [ править ]

Типовая безопасность обычно является требованием для любого игрушечного языка, предлагаемого в академических исследованиях языков программирования. С другой стороны, многие языки слишком велики для доказательств безопасности типов, созданных человеком, поскольку часто требуют проверки тысяч случаев. Тем не менее было доказано , что некоторые языки, такие как Standard ML , в котором строго определена семантика, соответствуют одному определению безопасности типов. [3] Некоторые другие языки , такие как Haskell являются Считается , [ обсудить ] встретить некоторое определение безопасности типа, при условии , некоторые «бежать» функция не используется (например , в Haskell unsafePerformIO, используется для «выхода» из обычной ограниченной среды, в которой возможен ввод-вывод, обходит систему типов и поэтому может использоваться для нарушения безопасности типов. [4] ) Воспроизведение текста - еще один пример такой возможности "выхода". Независимо от свойств определения языка, определенные ошибки могут возникать во время выполнения из-за ошибок в реализации или в связанных библиотеках, написанных на других языках; такие ошибки могут сделать данный тип реализации небезопасным при определенных обстоятельствах. Ранняя версия виртуальной машины Sun Java была уязвима для такого рода проблем. [2]

Сильный и слабый набор текста [ править ]

Языки программирования часто в просторечии классифицируются как строго типизированные или слабо типизированные (также слабо типизированные) для обозначения определенных аспектов безопасности типов. В 1974 году Лисков и Зиллес определили строго типизированный язык как язык, в котором «всякий раз, когда объект передается от вызывающей функции к вызываемой функции, его тип должен быть совместим с типом, объявленным в вызываемой функции». [5] В 1977 году Джексон писал: «В строго типизированном языке каждая область данных будет иметь отдельный тип, и каждый процесс будет определять свои коммуникационные требования в терминах этих типов». [6] Напротив, слабо типизированный язык может давать непредсказуемые результаты или может выполнять неявное преобразование типов. [7]

Безопасность типов в объектно-ориентированных языках [ править ]

В объектно-ориентированных языках безопасность типов обычно присуща наличию системы типов . Это выражается в определениях классов.

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

Каждая функция, которая обменивается объектами, производными от определенного класса или реализующими конкретный интерфейс , будет придерживаться этого контракта: следовательно, в этой функции операции, разрешенные для этого объекта, будут только теми, которые определены методами класса, реализуемого объектом. Это гарантирует сохранение целостности объекта. [8]

Исключением являются объектно-ориентированные языки, которые позволяют динамически изменять структуру объекта или использовать отражение для изменения содержимого объекта, чтобы преодолеть ограничения, налагаемые определениями методов класса.

Проблемы безопасности ввода на определенных языках [ править ]

Ада [ править ]

Ada была разработана, чтобы быть подходящей для встраиваемых систем , драйверов устройств и других форм системного программирования , но также для поощрения безопасного программирования. Чтобы разрешить эти противоречивые цели, Ада ограничивает безопасность типов определенным набором специальных конструкций, имена которых обычно начинаются со строки Unchecked_ . Unchecked_Deallocation можно эффективно запретить для блока текста Ada, применив к этому блоку прагму Pure . Ожидается, что программисты будут использовать конструкции Unchecked_ очень осторожно и только при необходимости; программы, которые их не используют, безопасны по типу.

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

Ada2012 добавляет статически проверенные контракты к самому языку (в форме предварительных и постусловий , а также инвариантов типов).

C [ править ]

Язык программирования C является типобезопасным в ограниченном контексте; например, ошибка времени компиляции генерируется, когда делается попытка преобразовать указатель на структуру одного типа в указатель на структуру другого типа, если не используется явное приведение. Однако ряд очень распространенных операций небезопасны по типу; например, обычный способ печати целого есть нечто подобное printf("%d", 12), где %dговорит printfво время выполнения ожидать целочисленный аргумент. (Что-то вродеprintf("%s", 12), который сообщает функции ожидать указатель на символьную строку и при этом предоставляет целочисленный аргумент, может быть принят компиляторами, но приведет к неопределенным результатам.) Это частично смягчается некоторыми компиляторами (такими как gcc), проверяющими соответствие типов между аргументы printf и строки формата.

Кроме того, C, как и Ada, предоставляет неуказанные или неопределенные явные преобразования; и, в отличие от Ada, идиомы, использующие эти преобразования, очень распространены и помогли создать репутацию C небезопасного типа. Например, стандартный способ выделить память в куче - вызвать функцию выделения памяти, например malloc, с аргументом, указывающим, сколько байтов требуется. Функция возвращает нетипизированный указатель (тип void *), который вызывающий код должен явно или неявно привести к соответствующему типу указателя. Предварительно стандартизованные реализации C требовали для этого явного приведения, поэтому код стал общепринятой практикой. [9](struct foo *) malloc(sizeof(struct foo))

C ++ [ править ]

Некоторые особенности C ++, которые способствуют большей типобезопасности кода:

  • Оператор new возвращает указатель типа на основе операнда, тогда как malloc возвращает указатель void.
  • Код C ++ может использовать виртуальные функции и шаблоны для достижения полиморфизма без пустых указателей.
  • Более безопасные операторы приведения, такие как динамическое приведение, которое выполняет проверку типа во время выполнения.
  • Строго типизированные перечисления C ++ 11 не могут быть неявно преобразованы в целые числа или другие типы перечисления или из них.
  • Явные конструкторы C ++ и операторы явного преобразования C ++ 11 предотвращают неявное преобразование типов.

C # [ править ]

C # является типобезопасным (но не статически типобезопасным). Он поддерживает нетипизированные указатели, но к ним нужно обращаться с помощью ключевого слова "unsafe", которое может быть запрещено на уровне компилятора. Он имеет встроенную поддержку проверки приведения во время выполнения. Приведения можно проверить с помощью ключевого слова as, которое вернет пустую ссылку, если приведение недопустимо, или с помощью приведения в стиле C, которое вызовет исключение, если приведение недопустимо. См. Операторы преобразования C Sharp .

Чрезмерная зависимость от типа объекта (от которого происходят все другие типы) чревата риском свести на нет цель системы типов C #. Обычно лучше отказаться от ссылок на объекты в пользу универсальных шаблонов, подобных шаблонам в C ++ и универсальным шаблонам в Java .

Java [ править ]

Язык Java разработан для обеспечения безопасности типов. В Java все происходит внутри объекта, и каждый объект является экземпляром класса .

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

Иногда часть безопасности типов реализуется косвенно: например, класс BigDecimal представляет число с плавающей запятой произвольной точности, но обрабатывает только числа, которые могут быть выражены с помощью конечного представления. Операция BigDecimal.divide () вычисляет новый объект как деление двух чисел, выраженных как BigDecimal.

В этом случае, если у деления нет конечного представления, как при вычислении, например, 1/3 = 0,33333 ..., метод div () может вызвать исключение, если для операции не определен режим округления. Следовательно, библиотека, а не язык, гарантирует, что объект соблюдает контракт, подразумеваемый в определении класса.

Стандартный ML [ править ]

SML имеет строго определенную семантику и известен как типобезопасный. Однако некоторые реализации SML, включая Standard ML of New Jersey (SML / NJ), его синтаксические варианты Mythryl и Mlton , предоставляют библиотеки, которые предлагают определенные небезопасные операции. Эти средства часто используются вместе с интерфейсами внешних функций этих реализаций для взаимодействия с кодом, отличным от ML (например, библиотеками C), которым могут потребоваться данные, размещенные определенным образом. Другим примером является сам интерактивный верхний уровень SML / NJ , который должен использовать небезопасные операции для выполнения кода ML, введенного пользователем.

Модула-2 [ править ]

Modula-2 - это строго типизированный язык с философией проектирования, требующей, чтобы любые небезопасные объекты были явно помечены как небезопасные. Это достигается «перемещением» таких средств во встроенную псевдобиблиотеку под названием SYSTEM, откуда они должны быть импортированы, прежде чем их можно будет использовать. Таким образом, импорт делает видимым, когда такие средства используются. К сожалению, это не было последовательно реализовано в отчете на языке оригинала и его реализации. [10] По-прежнему оставались небезопасные средства, такие как синтаксис приведения типов и записи вариантов (унаследованные от Паскаля), которые можно было использовать без предварительного импорта. [11] Сложность при перемещении этих средств в псевдомодуль SYSTEM заключалась в отсутствии какого-либо идентификатора для средства, который затем можно было бы импортировать, поскольку можно импортировать только идентификаторы, но не синтаксис.

 СИСТЕМА ИМПОРТА ;  (* позволяет использовать некоторые небезопасные средства: *) слово VAR  : SYSTEM . СЛОВО ; адрес : СИСТЕМА . АДРЕС ; адрес : = СИСТЕМА . ADR ( слово );       (* но синтаксис приведения типов можно использовать без такого импорта *) VAR  i  :  INTEGER ;  n  :  КАРДИНАЛ ; n  : =  КАРДИНАЛ ( i );  (* или *)  i  : =  ЦЕЛОЕ ( n );

Стандарт ISO Modula-2 исправил это для средства приведения типов, изменив синтаксис приведения типов в функцию CAST, которую необходимо импортировать из псевдомодуля SYSTEM. Однако другие небезопасные средства, такие как записи вариантов, оставались доступными без какого-либо импорта из псевдомодуля SYSTEM. [12]

 СИСТЕМА ИМПОРТА ; VAR  i  :  INTEGER ;  n  :  КАРДИНАЛ ; i  : =  СИСТЕМА . CAST ( ЦЕЛОЕ ЧИСЛО ;  n );  (* Тип приведен в ISO Modula-2 *)

В недавней редакции языка строго применялась оригинальная философия дизайна. Во-первых, псевдомодуль SYSTEM был переименован в UNSAFE, чтобы прояснить небезопасный характер импортированных оттуда средств. Затем все оставшиеся небезопасные объекты были либо полностью удалены (например, вариантные записи), либо перемещены в псевдомодуль UNSAFE. Для объектов, где нет идентификатора, который можно было бы импортировать, были введены разрешающие идентификаторы. Чтобы включить такую ​​возможность, соответствующий ей разрешающий идентификатор должен быть импортирован из псевдомодуля UNSAFE. На языке не остается небезопасных объектов, не требующих импорта из UNSAFE. [11]

 НЕБЕЗОПАСНЫЙ ИМПОРТ ; VAR  i  :  INTEGER ;  n  :  КАРДИНАЛ ; i  : =  НЕ БЕЗОПАСНО . CAST ( ЦЕЛОЕ ЧИСЛО ;  n );  (* Типовое приведение в Modula-2 Revision 2010 *)ОТ  UNSAFE  ИМПОРТНОГО  FFI ;  (* включение идентификатора для средства интерфейса внешней функции *) <* FFI = "C" *>  (* прагма для интерфейса внешней функции к C *)

Паскаль [ править ]

У Паскаля был ряд требований к безопасности типов, некоторые из которых сохранены в некоторых компиляторах. Если компилятор Паскаля предписывает «строгую типизацию», две переменные не могут быть присвоены друг другу, если они не совместимы (например, преобразование целого числа в действительное) или присвоены одному и тому же подтипу. Например, если у вас есть следующий фрагмент кода:

тип  TwoTypes  =  запись  I :  Integer ;  Q :  Реальный ;  конец ; DualTypes  =  запись  I :  целое число ;  Q :  Реальный ;  конец ;var  T1 ,  T2 :  TwoTypes ;  D1 ,  D2 :  DualTypes ;

Под строгим ввода, переменная определяется как TwoTypes это не совместимо с DualTypes (потому что они не являются идентичными, даже при том , что компоненты этого определенного пользователем типа являются идентичными) и присваивание Т1: = D2; незаконно. Присвоение T1: = T2; будет законным, потому что подтипы, для которых они определены , идентичны. Однако такое присвоение, как T1.Q: = D1.Q; будет законным.

Common Lisp [ править ]

В общем, Common Lisp - это типобезопасный язык. Компилятор Common Lisp отвечает за вставку динамических проверок для операций, безопасность типов которых не может быть доказана статически. Однако программист может указать, что программа должна быть скомпилирована с более низким уровнем динамической проверки типов. [13] Программа, скомпилированная в таком режиме, не может считаться типобезопасной.

Примеры C ++ [ править ]

В следующих примерах показано, как операторы приведения C ++ могут нарушить безопасность типов при неправильном использовании. Первый пример показывает, как могут быть неправильно приведены основные типы данных:

#include  <iostream>используя  пространство имен  std ;int  main  ()  {  int  ival  =  5 ;  // целочисленное значение  float  fval  =  reinterpret_cast < float &> ( ival );  // переинтерпретировать битовый шаблон  соиЬ  <<  fval  <<  епсИ ;  // вывод целого числа как float  return  0 ; }

В этом примере reinterpret_castявно запрещает компилятору выполнять безопасное преобразование из целого числа в значение с плавающей запятой. [14] Когда программа запускается, она выводит мусорное значение с плавающей запятой. Этой проблемы можно было избежать, написав вместо этогоfloat fval = ival;

В следующем примере показано, как ссылки на объекты могут быть неправильно понижены:

#include  <iostream>используя  пространство имен  std ;class  Parent  { public :  virtual  ~ Parent ()  {}  // виртуальный деструктор для RTTI };class  Child1  :  общедоступный  родитель  { общедоступный :  int  a ; };class  Child2  :  public  Parent  { public :  float  b ; };int  main  ()  {  Child1  c1 ;  c1 . а  =  5 ;  Родительский  &  p  =  c1 ;  // вентиляционный всегда безопасно  Child2  &  c2  =  static_cast < Child2 &> ( р );  // недопустимое понижающее  преобразование cout  <<  c2 . b  <<  endl ;  // выведет мусорные данные  return  0 ; }

В двух дочерних классах есть члены разных типов. При понижении значения указателя родительского класса на указатель дочернего класса результирующий указатель может не указывать на действительный объект правильного типа. В этом примере это приводит к тому, что печатается мусорное значение. Проблему можно было бы избежать, заменив static_castпри dynamic_castтом , что бросает исключение на недействительных слепков. [15]

См. Также [ править ]

  • Теория типов

Заметки [ править ]

  1. ^ Милнер, Робин (1978), "Теория типа полиморфизма в программировании", журнал компьютерных и системных наук , 17 (3): 348-375, DOI : 10,1016 / 0022-0000 (78) 90014-4
  2. ^ a b Сарасват, Виджай (1997-08-15). «Java не является типобезопасным» . Проверено 8 октября 2008 .
  3. ^ Стандартный ML . Smlnj.org. Проверено 2 ноября 2013.
  4. ^ "System.IO.Unsafe" . Руководство по библиотекам GHC: base-3.0.1.0 . Архивировано из оригинала на 2008-07-05 . Проверено 17 июля 2008 .
  5. ^ Лисков, B; Зиллес, S (1974). «Программирование с абстрактными типами данных». Уведомления ACM SIGPLAN . 9 (4): 50–59. CiteSeerX 10.1.1.136.3043 . DOI : 10.1145 / 942572.807045 . 
  6. ^ Джексон, К. (1977). Параллельная обработка и модульное построение программного обеспечения . Дизайн и реализация языков программирования . Конспект лекций по информатике. 54 . С. 436–443. DOI : 10.1007 / BFb0021435 . ISBN 3-540-08360-Х.
  7. ^ «CS1130. Переход к объектно-ориентированному программированию. - Весна 2012 - самостоятельная версия» . Корнельский университет, факультет компьютерных наук. 2005. Архивировано из оригинала на 2005 . Проверено 23 ноября 2015 .
  8. ^ Безопасность типов, следовательно, также является вопросом хорошего определения класса: общедоступные методы, которые изменяют внутреннее состояние объекта, должны сохранять целостность объекта.
  9. ^ Керниган ; Деннис М. Ричи (март 1988 г.). Язык программирования C (2-е изд.). Энглвуд Клиффс, Нью-Джерси : Prentice Hall . п. 116 . ISBN 978-0-13-110362-7. В C правильный метод - объявить, что malloc возвращает указатель на void, а затем явно привести указатель к желаемому типу с помощью приведения.
  10. Никлаус Вирт (1985). Программирование в Модуле-2 . Springer Verlag.
  11. ^ a b «Разделение безопасных и небезопасных помещений» . Проверено 24 марта 2015 года .
  12. ^ "Справочник по языку ISO Modula-2" . Проверено 24 марта 2015 года .
  13. ^ "Common Lisp HyperSpec" . Проверено 26 мая 2013 года .
  14. ^ http://en.cppreference.com/w/cpp/language/reinterpret_cast
  15. ^ http://en.cppreference.com/w/cpp/language/dynamic_cast

Ссылки [ править ]

  • Пирс, Бенджамин С. (2002). Типы и языки программирования . MIT Press. ISBN 978-0-262-16209-8.
  • «Тип безопасный» . Wiki Портлендского репозитория паттернов .
  • Райт, Эндрю К .; Маттиас Фелляйзен (1994). «Синтаксический подход к правильности типа» . Информация и вычисления . 115 (1): 38–94. DOI : 10.1006 / inco.1994.1093 .
  • Макракис, Ставрос (апрель 1982 г.). «Безопасность и мощь». Примечания по разработке программного обеспечения ACM SIGSOFT . 7 (2): 25–26. DOI : 10.1145 / 1005937.1005941 .