Linguagens de programação seguras (PL) estão ganhando popularidade. Gostaria de saber qual é a definição formal de PL seguro. Por exemplo, C não é seguro, mas Java é seguro. Suspeito que a propriedade “segura” deva ser aplicada a uma implementação de PL e não ao próprio PL. Nesse caso, vamos discutir uma definição de implementação segura de PL. Minhas próprias tentativas de formalizar essa noção levaram a um resultado estranho, então eu gostaria de ouvir outras opiniões. Por favor, não diga que todo PL possui comandos não seguros. Sempre podemos pegar um subconjunto seguro.
programming-languages
beroal
fonte
fonte
Respostas:
Quando chamamos um idioma de "seguro" em algum aspecto , isso significa formalmente que há uma prova de que nenhum programa bem formado no idioma pode fazer algo que consideramos perigoso. A palavra "seguro" também é usada menos formalmente, mas é isso que as pessoas aqui entendem como sua pergunta. Existem muitas definições diferentes de propriedades que queremos que uma linguagem "segura" tenha.
Alguns importantes são:
A definição de “sonoridade de tipo” de Andrew Wright e Matthias Felleisen , citada em muitos lugares (incluindo a Wikipedia) como uma definição aceita de “segurança de tipo”, e sua prova de 1994 de que um subconjunto de ML atende a ela.
Michael Hicks lista várias definições de "segurança da memória" aqui . Algumas são listas de tipos de erros que não podem ocorrer e outras são baseadas no tratamento de ponteiros como recursos. O Java garante que nenhum desses erros seja possível (a menos que você use explicitamente um recurso marcado
unsafe
) fazendo com que um coletor de lixo gerencie todas as alocações e desalocações. O Rust faz a mesma garantia (novamente, a menos que você marque explicitamente o código comounsafe
), através de seu sistema de tipo afim, que exige que uma variável seja de propriedade ou emprestada antes de ser usada no máximo uma vez.Da mesma forma, o código de thread-safe é geralmente definido como um código que não pode exibir certos tipos de bugs envolvendo threads e memória compartilhada, incluindo raças de dados e deadlocks. Essas propriedades geralmente são aplicadas no nível da linguagem: Rust garante que as corridas de dados não ocorram em seu sistema de tipos, o C ++ garante que seus
std::shared_ptr
ponteiros inteligentes para os mesmos objetos em vários threads não excluam um objeto prematuramente ou falhem em excluí-lo quando a última referência para que seja destruído, C e C ++ adicionalmente possuematomic
variáveis embutidas na linguagem, com operações atômicas garantidas para impor certos tipos de consistência de memória, se usadas corretamente. O MPI restringe a comunicação entre processos a mensagens explícitas e o OpenMP possui sintaxe para garantir que o acesso a variáveis de diferentes threads seja seguro.A propriedade que a memória nunca vazará é geralmente chamada de espaço seguro. A coleta automática de lixo é um recurso de idioma para garantir isso.
Muitos idiomas garantem que suas operações terão resultados bem definidos e que seus programas serão bem-comportados. Como o supercat deu um exemplo acima, C faz isso para aritmética não assinada (garantida a quebra com segurança), mas não aritmética assinada (onde o estouro é permitido para causar erros arbitrários, porque C precisava suportar CPUs que fazem coisas totalmente diferentes quando aritméticas assinadas transborda), mas o idioma algumas vezes converte silenciosamente quantidades não assinadas em quantidades assinadas.
As linguagens funcionais têm um grande número de invariantes que qualquer programa bem formado garante, por exemplo, que funções puras não podem causar efeitos colaterais. Estes podem ou não ser descritos como "seguros".
Alguns idiomas, como SPARK ou OCaml, foram projetados para facilitar a comprovação da correção do programa. Isso pode ou não ser descrito como "seguro" contra bugs.
Prova que um sistema não pode violar um modelo formal de segurança (daí a piada, "Qualquer sistema que seja comprovadamente seguro provavelmente não é".)
fonte
Não existe uma definição formal de "linguagem de programação segura"; é uma noção informal. Em vez disso, os idiomas que alegam fornecer segurança geralmente fornecem uma declaração formal precisa de que tipo de segurança está sendo reivindicada / garantida / fornecida. Por exemplo, o idioma pode fornecer segurança de tipo, segurança de memória ou outra garantia semelhante.
fonte
double
oulong
enquanto está sendo modificado em outro encadeamento, que não garante que não produza metade de um valor misturado de alguma maneira não especificada com metade do outro), mas a especificação da API no entanto, possui comportamento indefinido em alguns casos.Se você conseguir colocar uma cópia dos Tipos e Linguagens de Programação de Benjamin Pierce , na introdução, ele terá uma boa visão geral sobre várias perspectivas do termo "linguagem segura".
Uma interpretação proposta do termo que você pode achar interessante é:
Portanto, eu hesitaria em usar o termo "inseguro" para me referir a uma implementação de linguagem de programação. Se um termo indefinido em um idioma produz um comportamento diferente em diferentes implementações, uma das implementações pode gerar um comportamento mais esperado, mas eu não chamaria de "seguro".
fonte
Seguro não é binário, é um continuum .
Informalmente, segurança significa oposição a bugs, sendo os dois mais mencionados:
Essas não são as únicas classes de bugs que as linguagens impedem, a liberdade de corrida de dados ou a liberdade de conflito é bastante desejável, as provas de correção são muito boas, etc ...
Porém, programas simplesmente incorretos raramente são considerados "inseguros" (apenas bugs), e o termo segurança é geralmente reservado para garantias que afetam nossa capacidade de raciocinar sobre um programa. Assim, C, C ++ ou Go, com comportamento indefinido, não são seguros.
E, é claro, existem idiomas com subconjuntos inseguros (Java, Rust, ...) que definem propositadamente áreas em que o desenvolvedor é responsável por manter as garantias de idioma e o compilador está no modo "hands-off". As línguas ainda são geralmente ditas seguras , apesar dessa escotilha de escape, uma definição pragmática.
fonte
Obj.magic
no Ocaml). E, na prática, estes são realmente necessáriosEmbora eu não concorde com a resposta da DW, acho que ela deixa uma parte do "seguro" não endereçada.
Como observado, existem vários tipos de segurança promovidos. Eu acredito que é bom entender por que existem várias noções. Cada noção está associada à idéia de que os programas sofrem especialmente com uma determinada classe de erros e que os programadores não seriam capazes de cometer esse tipo específico de erro se a linguagem impedisse o programador de fazê-lo.
Deve-se notar que essas noções diferentes têm, portanto, diferentes classes de bugs, e essas classes não são mutuamente exclusivas nem cobrem todas as formas de bugs. Apenas para pegar os 2 exemplos de DW, a questão de saber se um determinado local de memória contém um determinado objeto é uma questão de segurança de tipo e segurança de memória.
Uma crítica adicional a "linguagens seguras" decorre da observação de que a proibição de certas construções consideradas perigosas deixa o programador com a necessidade de apresentar alternativas. Empiricamente, a segurança é melhor alcançada por boas bibliotecas. o uso de código já testado em campo evita que você cometa novos bugs.
fonte
Uma diferença fundamental entre C e Java é que, se evitarmos certos recursos facilmente identificáveis do Java (por exemplo, aqueles no
Unsafe
espaço de nomes), todas as ações possíveis que se possa tentar - incluindo as "errôneas" - terão um alcance limitado de resultados possíveis . Embora isso limite o que se pode fazer em Java - pelo menos sem usar oUnsafe
namespace, também é possível limitar o dano que pode ser causado por um programa incorreto ou, mais importante, por um programa que processaria corretamente arquivos válidos, mas não é particularmente protegido contra arquivos errados.Tradicionalmente, os compiladores C processavam muitas ações da maneira definida pelo padrão em casos "normais", enquanto processavam muitos casos de canto "de uma maneira característica do ambiente". Se alguém estivesse usando uma CPU que poderia causar um curto-circuito e pegar fogo se ocorresse um excesso numérico e quisesse evitar que a CPU pegasse fogo, seria necessário escrever um código para evitar o excesso numérico. Se, no entanto, alguém estivesse usando uma CPU que truncaria perfeitamente os valores da maneira complemento dois, não seria necessário evitar estouros nos casos em que esse truncamento resultaria em comportamento aceitável.
O C moderno leva as coisas um passo adiante: mesmo que alguém tenha como alvo uma plataforma que naturalmente definiria um comportamento para algo como excesso numérico em que o Padrão não impõe requisitos, o excesso em uma parte do programa pode afetar o comportamento de outras partes do programa de maneira arbitrária, não vinculada às leis do tempo e da causalidade. Por exemplo, considere algo como:
Um compilador C "moderno", dado algo como o acima, pode concluir que, como o cálculo de x * x excederia se x for maior que 46340, ele poderá executar a chamada para "foo" incondicionalmente. Observe que, mesmo que seja aceitável que um programa termine de forma anormal se x estiver fora da faixa ou se a função retornar qualquer valor, nesses casos, chamar foo () com uma faixa fora da faixa x pode causar danos muito além qualquer uma dessas possibilidades. O tradicional C não forneceria nenhum equipamento de segurança além do fornecido pelo programador e pela plataforma subjacente, mas permitiria que o equipamento de segurança limite os danos causados por situações inesperadas. O C moderno ignora qualquer equipamento de segurança que não seja 100% eficaz em manter tudo sob controle.
fonte
int
tiver 32 bits,x
será promovido a assinadoint
. A julgar pela lógica, os autores da Norma esperavam que implementações não estranhas tratassem tipos assinados e não assinados de maneira equivalente fora de alguns casos específicos, mas o gcc às vezes "otimiza" de maneiras que quebram se a multiplicaruint16_t
poruint16_t
um resultado além de INT_MAX , mesmo quando o resultado estiver sendo usado como um valor não assinado.-Wconversion
.return x+1;
que não devem ser problemáticas, e transmitir o resultado para uint32_t sufocaria a mensagem sem corrigir o problema.Existem várias camadas de correção em um idioma. Em ordem crescente de abstração:
No próximo nível, erros detectados no tempo de compilação em vez de no tempo de execução tornam um idioma mais seguro. Um programa sintaticamente correto também deve estar semanticamente correto, tanto quanto possível. Obviamente, o compilador não pode conhecer o quadro geral, portanto isso diz respeito ao nível de detalhe. Tipos de dados fortes e expressivos são um aspecto da segurança nesse nível. Pode-se dizer que o idioma deve dificultar alguns tipos de erros(erros de tipo, acesso fora dos limites, variáveis não inicializadas etc.). Informações do tipo em tempo de execução, como matrizes que carregam informações de comprimento, evitam erros. Programei o Ada 83 na faculdade e descobri que um programa Ada compilador normalmente continha talvez uma ordem de magnitude menos erros que o programa C correspondente. Apenas considere a capacidade de Ada de definir tipos inteiros que não são atribuíveis sem conversão explícita: Naves espaciais inteiras caíram porque pés e metros estavam confusos, o que se poderia evitar com Ada.
No próximo nível, o idioma deve fornecer meios para evitar o código padrão. Se você precisar escrever seus próprios contêineres, ou sua classificação ou concatenação, ou se precisar escrever seus próprios
string::trim()
erros, cometerá erros. Como o nível de abstração aumenta, esse critério envolve o idioma apropriado e a biblioteca padrão do idioma.Atualmente, o idioma deve fornecer meios para programação simultânea no nível do idioma. É difícil acertar a simultaneidade e talvez impossível fazer corretamente sem o suporte ao idioma.
A linguagem deve fornecer meios para modularização e colaboração. Os tipos fortes, elaborados e definidos pelo usuário acima ajudam a criar APIs expressivas.
De certa forma ortogonalmente, a definição da linguagem deve ser inteligível; a linguagem e as bibliotecas devem estar bem documentadas. Documentação incorreta ou ausente leva a programas ruins e errados.
1 Mas, como geralmente a correção da máquina virtual não pode ser comprovada, essas linguagens podem paradoxalmente não ser adequadas para requisitos de segurança muito rígidos.
fonte
Todo idioma que eu conheço tem maneiras de escrever programas ilegais que podem ser (compilados e) executados. E todo idioma que eu conheço tem um subconjunto seguro. Então, qual é a sua pergunta mesmo?
A segurança é multidimensional e subjetiva.
Alguns idiomas têm muitas operações "inseguras". Outros têm menos operações desse tipo. Em alguns idiomas, a maneira padrão de fazer algo é inerentemente insegura. Em outros, a maneira padrão é segura. Em alguns idiomas, há um subconjunto "inseguro" explícito. Em outros idiomas, não existe esse subconjunto.
Em alguns idiomas, "segurança" refere-se exclusivamente à segurança da memória - um serviço oferecido pela biblioteca padrão e / ou o tempo de execução em que as violações do acesso à memória são dificultadas ou impossíveis. Em outros idiomas, "segurança" inclui explicitamente a segurança do thread. Em outros idiomas, "segurança" refere-se à garantia de que um programa não falha (um requisito que inclui não permitir exceções não capturadas de qualquer tipo). Finalmente, em muitos idiomas, "segurança" refere-se à segurança de tipo - se o sistema de tipos for consistente de certas maneiras, é dito que ele é "sólido" (aliás, Java e C # não possuem sistemas de tipos completamente sonoros).
E em alguns idiomas, todos os significados diferentes de "segurança" são considerados subconjuntos do tipo segurança (por exemplo, Rust e Pony atingem a segurança do encadeamento através das propriedades do sistema de tipos).
fonte
Essa resposta é um pouco mais ampla. As palavras segurança e segurança foram mutiladas nas últimas décadas por certas partes politicamente orientadas da sociedade de língua inglesa, de modo que seu uso comum quase não tem definição. No entanto, para assuntos técnicos, ainda volto a definir "segurança" e "seguro" como: um dispositivo que impede o uso não intencional de algo ou que dificulta substancialmente o uso acidental e o estado de estar sob a proteção desse dispositivo .
Portanto, uma linguagem segura possui algum dispositivo para limitar uma classe específica de bugs. É claro que os limites são inconvenientes ou até incapazes em alguns casos, e isso não significa que idiomas "inseguros" resultem em bugs. Por exemplo, não tenho rolhas de segurança nos garfos e, durante décadas, consegui, sem muito esforço, evitar esfaquear meus olhos enquanto comia. Certamente menos esforço do que seria gasto com as rolhas. Portanto, a segurança tem um custo com o qual deve ser julgada. (garfo de cortiça é uma referência a um personagem de Steve Martin)
fonte