Безопасные языки программирования (PL) набирают популярность. Интересно, каково формальное определение безопасного PL. Например, C небезопасен, но Java безопасен. Я подозреваю, что свойство «safe» должно применяться к реализации PL, а не к самой PL. Если это так, давайте обсудим определение безопасной реализации PL. Мои собственные попытки формализовать это понятие привели к странному результату, поэтому я хотел бы услышать другие мнения. Пожалуйста, не говорите, что у каждого PL есть небезопасные команды. Мы всегда можем взять безопасное подмножество.
programming-languages
beroal
источник
источник
Ответы:
Когда мы называем язык «безопасным» в некотором отношении , это формально означает, что есть доказательство того, что ни одна правильно сформированная программа на языке не может сделать то, что мы считаем опасным. Слово «безопасный» также используется менее формально, но именно здесь люди понимают, что означает ваш вопрос. Есть много разных определений свойств, которые мы хотим, чтобы «безопасный» язык имел.
Несколько важных из них:
Определение Эндрю Райтом и Матиасом Феллайзеном «правильности типа» , которое во многих местах (включая Википедию) приводится в качестве общепринятого определения «безопасности типов», и их доказательство того, что подмножество ML соответствует этому определению в 1994 году.
Майкл Хикс перечисляет здесь несколько определений «безопасности памяти» . Некоторые представляют собой списки типов ошибок, которые не могут возникать, а некоторые основаны на обработке указателей как возможностей. Java гарантирует, что ни одна из этих ошибок не возможна (если вы явно не используете функцию, помеченную
unsafe
), если сборщик мусора будет управлять всеми выделениями и освобождениями. Rust дает ту же гарантию (опять же, если вы явно не пометите код какunsafe
) через свою систему аффинных типов, которая требует, чтобы переменная была либо принадлежала, либо заимствована, прежде чем ее можно будет использовать не более одного раза.Аналогично, потокобезопасный код обычно определяется как код, который не может отображать определенные виды ошибок, связанных с потоками и общей памятью, включая гонки данных и взаимоблокировки. Эти свойства часто применяются на уровне языка: Rust гарантирует, что гонки данных не могут происходить в его системе типов, C ++ гарантирует, что его
std::shared_ptr
умные указатели на одни и те же объекты в нескольких потоках не будут удалять объект преждевременно или не удалят его при последней ссылке поскольку он уничтожен, C и C ++ дополнительно имеютatomic
встроенные в язык переменные с атомарными операциями, которые гарантированно обеспечивают определенную согласованность памяти при правильном использовании. MPI ограничивает межпроцессное взаимодействие явными сообщениями, а OpenMP имеет синтаксис для обеспечения безопасности доступа к переменным из разных потоков.Свойство, что память никогда не утечет, часто называют безопасным для пространства. Автоматическая сборка мусора является одной из языковых функций, чтобы гарантировать это.
Многие языки гарантируют, что его операции будут иметь четко определенные результаты, а его программы будут хорошо себя вести. Как supercat привел в качестве примера выше, C делает это для арифметики без знака (гарантированно безопасное выполнение), но не для арифметики со знаком (где переполнение может вызывать произвольные ошибки, потому что C необходимо поддерживать процессоры, которые делают совершенно разные вещи, когда арифметика со знаком переполняется), но затем язык иногда тихо преобразует неподписанные количества в подписанные.
Функциональные языки имеют большое количество инвариантов, которые гарантированно поддержит любая правильно сформированная программа, например, что чистые функции не могут вызывать побочные эффекты. Они могут или не могут быть описаны как «безопасные».
Некоторые языки, такие как SPARK или OCaml, предназначены для облегчения проверки правильности программы. Это может или не может быть описано как «безопасный» от ошибок.
Доказательство того, что система не может нарушать формальную модель безопасности (отсюда и фраза «Любая система, которая доказуемо безопасна, вероятно, не является»).
источник
Не существует формального определения «безопасного языка программирования»; это неформальное понятие. Скорее, языки, которые утверждают, что обеспечивают безопасность, обычно предоставляют точное формальное заявление о том, какая безопасность заявляется / гарантируется / предоставляется. Например, язык может обеспечивать безопасность типов, безопасность памяти или некоторые другие подобные гарантии.
источник
double
илиlong
когда она изменяется в другом потоке, что не гарантирует, что не будет получено половина одного значения, смешанного каким-то неуказанным способом с половиной другого), но спецификация API однако имеет неопределенное поведение в некоторых случаях.Если вы сможете получить экземпляр « Типов и языков программирования» Бенджамина Пирса , во введении у него будет хороший обзор различных точек зрения на термин «безопасный язык».
Одна из предлагаемых интерпретаций этого термина, которая может вас заинтересовать:
Таким образом, я бы не хотел использовать термин «небезопасный» для обозначения реализации языка программирования. Если неопределенный термин в языке вызывает различное поведение в разных реализациях, одна из реализаций может привести к тому, что поведение продукта будет более ожидаемым, но я бы не назвал его «безопасным».
источник
Сейф не бинарный, это континуум .
Неформально говоря, безопасность подразумевает противодействие ошибкам, из которых 2 чаще всего упоминаются:
Это не единственные классы ошибок, которые предотвращают языки, свобода гонки данных или тупика довольно желательна, доказательства правильности довольно приятны и т. Д ...
Однако просто некорректные программы редко считаются «небезопасными» (только с ошибками), а термин «безопасность» обычно зарезервирован для гарантий, влияющих на нашу способность рассуждать о программе. Таким образом, C, C ++ или Go, имеющие неопределенное поведение, небезопасны.
И, конечно же, есть языки с небезопасными подмножествами (Java, Rust, ...), которые целенаправленно разграничивают области, в которых разработчик отвечает за поддержание языковых гарантий, а компилятор находится в режиме «от руки». Языки, как правило, все еще называют безопасными , несмотря на это практическое определение.
источник
Obj.magic
в Ocaml). И на практике это действительно нужноХотя я не согласен с ответом DW, я думаю, что он оставляет одну часть «безопасного» без внимания.
Как уже отмечалось, существует несколько видов безопасности. Я считаю, что хорошо понимать, почему существует множество понятий. Каждое понятие связано с идеей, что программы страдают особенно от определенного класса ошибок, и что программисты не смогут сделать этот специфический тип ошибки, если язык заблокирует программиста от этого.
Следует отметить, что эти разные понятия, следовательно, имеют разные классы ошибок, и эти классы не являются взаимоисключающими, и эти классы не охватывают все формы ошибок. Просто чтобы взять два примера DW, вопрос о том, содержит ли определенная область памяти определенный объект, является одновременно вопросом безопасности типов и безопасности памяти.
Дальнейшая критика «безопасных языков» следует из наблюдения, что запрет определенных конструкций, считающихся опасными, оставляет программисту необходимость придумывать альтернативы. Опытным путем безопасность лучше достигается хорошими библиотеками. использование кода, уже проверенного в полевых условиях, избавляет вас от новых ошибок.
источник
Принципиальное различие между C и Java заключается в том, что если не использовать некоторые легко идентифицируемые возможности Java (например, те, которые находятся в
Unsafe
пространстве имен), то каждое возможное действие, в том числе «ошибочные», будет иметь ограниченный диапазон возможных результатов. , Хотя это ограничивает то, что можно делать в Java - по крайней мере, без использованияUnsafe
пространства имен, оно также позволяет ограничить ущерб, который может быть вызван ошибочной программой или, что более важно, программой, которая будет правильно обрабатывать допустимые файлы, но не особо защищены от ошибочных.Традиционно компиляторы C обрабатывали бы многие действия стандартным образом в «нормальных» случаях, в то же время обрабатывая многие угловые случаи «способом, характерным для среды». Если бы кто-то использовал процессор, который бы замыкался и загорался, если происходило переполнение чисел, и хотел бы избежать возгорания процессора, нужно было бы написать код, чтобы избежать переполнения числа. Однако, если бы кто-то использовал процессор, который бы совершенно удачно урезал значения в виде дополнения до двух, не нужно было избегать переполнений в тех случаях, когда такое усечение приводило бы к приемлемому поведению.
Современный C делает вещи на шаг впереди: даже если кто-то нацелен на платформу, которая естественным образом определяет поведение для чего-то вроде числового переполнения, когда Стандарт не налагает никаких требований, переполнение в одной части программы может повлиять на поведение других частей Программа произвольным образом не связана законами времени и причинности. Например, рассмотрим что-то вроде:
«Современный» C-компилятор, имеющий что-то похожее на вышесказанное, может заключить, что, поскольку вычисление x * x будет переполнено, если x больше 46340, он может выполнить вызов «foo» безоговорочно. Обратите внимание, что даже если было бы приемлемо прекратить ненормальное завершение программы, если x выходит за пределы диапазона, или если в таких случаях функция должна возвращать любое значение, вызов foo () с x вне диапазона может привести к повреждению, намного превышающему любая из этих возможностей. Традиционный C не обеспечил бы никакой защитной экипировкой сверх того, что поставил программист и соответствующая платформа, но позволил бы защитной экипировке ограничить ущерб от неожиданных ситуаций. Modern C обойдет любое защитное снаряжение, которое не на 100% эффективно держит все под контролем.
источник
int
32 бита, тоx
будет повышен до подписиint
. Судя по обоснованию, авторы стандарта ожидали, что не странные реализации будут обрабатывать подписанные и неподписанные типы эквивалентным образом вне некоторых конкретных случаев, но gcc иногда «оптимизирует» способами, которые ломаются, еслиuint16_t
приuint16_t
умножении на a получается результат, превосходящий INT_MAX. , даже когда результат используется в качестве значения без знака.-Wconversion
.return x+1;
отмечать множество ситуаций, например, которые не должны быть проблематичными, и приведение результата к uint32_t душит сообщение, не решая проблему.В языке есть несколько уровней правильности. В порядке увеличения абстракции:
На следующем уровне ошибки, обнаруженные во время компиляции, а не во время выполнения, делают язык более безопасным. Синтаксически правильная программа также должна быть максимально семантически правильной. Конечно, компилятор не может знать общую картину, так что это касается уровня детализации. Сильные и выразительные типы данных являются одним из аспектов безопасности на этом уровне. Можно сказать, что язык должен усложнять определенные ошибки(ошибки типа, вне доступа, неинициализированные переменные и т. д.). Информация о типе времени выполнения, такая как массивы, которые содержат информацию о длине, позволяет избежать ошибок. Я запрограммировал Ada 83 в колледже и обнаружил, что компиляция Ada-программы обычно содержит на порядок меньше ошибок, чем соответствующая C-программа. Просто возьмите способность Ады определять целочисленные типы, которые нельзя назначить без явного преобразования: целые космические корабли потерпели крушение из-за путаницы в футах и метрах, чего можно было бы избежать с помощью Ады.
На следующем уровне язык должен обеспечивать средства, чтобы избежать стандартного кода. Если вам нужно написать свои собственные контейнеры, или их сортировку, или их конкатенацию, или если вы должны написать свои собственные,
string::trim()
вы будете делать ошибки. Поскольку уровень абстракции повышается, этот критерий включает в себя как сам язык, так и стандартную библиотеку языка.В эти дни язык должен обеспечивать средства для параллельного программирования на уровне языка. Параллельность трудно понять правильно и, возможно, невозможно сделать правильно без языковой поддержки.
Язык должен обеспечивать средства для модульности и сотрудничества. Сильные, тщательно продуманные пользовательские типы сверху помогают создавать выразительные API.
Несколько ортогонально определение языка должно быть понятным; язык и библиотеки должны быть хорошо документированы. Плохая или отсутствующая документация приводит к плохим и неправильным программам.
1 Но поскольку обычно правильность виртуальной машины не может быть доказана, такие языки могут как-то парадоксально не подходить для очень строгих требований безопасности.
источник
На каждом языке, который я знаю, есть способы написания нелегальных программ, которые можно (компилировать и) запускать. И у каждого языка, который я знаю, есть безопасное подмножество. Итак, какой у тебя вопрос на самом деле?
Безопасность многомерна и субъективна.
В некоторых языках есть много операций, которые «небезопасны». У других таких операций меньше. В некоторых языках способ выполнения чего-либо по умолчанию небезопасен. В других способ по умолчанию безопасен. В некоторых языках существует явное «небезопасное» подмножество. В других языках такого подмножества нет вообще.
В некоторых языках «безопасность» относится исключительно к безопасности памяти - услуга, предлагаемая стандартной библиотекой, и / или среда выполнения, в которой нарушения доступа к памяти становятся трудными или невозможными. В других языках «безопасность» явно включает в себя безопасность потоков. На других языках «безопасность» относится к гарантии того, что программа не будет аварийно завершена (требование, которое включает недопущение каких-либо необработанных исключений). Наконец, во многих языках «безопасность» относится к безопасности типов - если система типов согласована определенным образом, она называется «надежной» (между прочим, Java и C # не имеют полностью звуковых систем типов).
А в некоторых языках все различные значения «безопасность» считаются подмножествами безопасности типов (например, Rust и Pony достигают безопасности потоков благодаря свойствам системы типов).
источник
Этот ответ немного шире. Слова «безопасность и безопасность» в последние десятилетия были изуродованы некоторыми политически ориентированными частями англоязычного общества, так что их общее использование почти не имеет определения. Тем не менее, по техническим предметам я все же возвращаюсь к определению «безопасность» и «безопасность» как: устройство, которое предотвращает непреднамеренное использование чего-либо или которое существенно затрудняет случайное использование, и состояние нахождения под защитой такого устройства ,
Таким образом, безопасный язык имеет какое-то устройство для ограничения определенного класса ошибок. Конечно, ограничения в некоторых случаях приводят к неудобствам или даже невозможности, и это не значит, что «небезопасные» языки приводят к ошибкам. Например, у меня нет защитных пробок на вилках, и в течение десятилетий мне удавалось без особых усилий избежать удара глазом во время еды. Конечно, меньше усилий, чем было бы потрачено на пробки. Таким образом, безопасность сопряжена с определенными затратами, о которых нужно судить. (пробка - это ссылка на персонажа Стива Мартина)
источник