Neste guia, explicaremos tudo o que você precisa saber sobre a ferrugem insegura . Vamos nos concentrar no seguinte:

Mitos sobre ferrugem insegura

Antes de explicarmos como e quando usar (ou não) inseguro no Rust, quero despachar alguns mitos persistentes sobre o código não seguro no Rust.

Mito nº 1: Todo código Rust não é seguro

Não. A distinção é sutil, mas o código Rust seguro não pode violar as garantias de segurança, contanto que nem o compilador nem o código inseguro no qual ele se baseia tenham quaisquer erros que permitam que isso aconteça. Portanto, ao contrário de outras linguagens de baixo nível, em que a segurança do código reside em cada linha de código mais a implementação do compilador, você pode reduzir consideravelmente a superfície de ataque necessária para auditar os erros.

O projeto RustBelt provou matematicamente que se você tiver uma parte do código seguro e uma parte do código inseguro que protege suas invariáveis, o código seguro não pode quebrar as garantias, contanto que o código inseguro não o permita.

Como um aparte, uma invariante é uma condição que não é alterada por todos os métodos de um tipo ou por todas as funções de um módulo.

Estatisticamente falando, menos de 1 por cento do código Rust encontrado em Crates.io-o que é provavelmente menos de 50 por cento do código lá fora, mas deveria ser uma amostra suficientemente representativa-é um código inseguro e muitos engradados não têm linha de código insegura .

Mito 2: Depende do código de biblioteca padrão, que contém muitos códigos não seguros

Sim, a biblioteca padrão tem código mais inseguro do que a caixa média, o que é de se esperar, já que muitas das abstrações que ela oferece não podem ser implementadas no Rust seguro de forma eficiente ou de todo. Além disso, podemos ter certeza de que a biblioteca padrão recebeu mais análises e, portanto, é mais confiável do que sua caixa média.

Isso não quer dizer que seja perfeito-afinal, bugs foram encontrados no passado. Ainda assim, há bons esforços para verificar e difundir grandes partes da biblioteca padrão, o que torna todo o código do Rust mais seguro.

Mito nº 3: depois de escrever inseguro , posso desligar o verificador de empréstimo, invocar o diabo, abrir um portal para a dimensão da dor, etc.

Não. O Rustonomicon lista meticulosamente os poderes adicionais que inseguro concede a você em troca para garantir a manutenção das invariáveis ​​de segurança nessa parte do código. Por exemplo, você pode:

  • Dereferência de ponteiros brutos
  • Chame funções inseguras (incluindo funções C, intrínsecas do compilador e o alocador bruto)
  • Implementar características inseguras
  • Mutate statics
  • Campos de acesso de união s

Não há ritual escuro e nem desligar nenhum verificador de empréstimo. No entanto, mesmo aqueles poderes aparentemente benignos podem ter consequências com as quais você deve estar ciente:

  • Não há garantia de que ponteiros brutos apontem para uma instância válida dos dados que pretendem apontar, então desreferenciá-los pode levar você a um terreno nulo (e provavelmente a uma falha de segmentação)-ou, pior, a uma confusão de tipo, onde você interpreta mal os dados, corrompendo o estado do seu programa com efeitos possivelmente exploráveis ​​
  • Chamar funções inseguras acarreta o risco de chamá-las com argumentos que não atendem aos requisitos de segurança, com efeitos possivelmente exploráveis ​​
  • Implementar características inseguras para tipos que falham em manter suas invariantes também pode levar os chamadores inadvertidamente, causando a falha de seus requisitos de segurança com efeitos possivelmente exploráveis ​​
  • A mutação de um estático ao observá-lo em outro encadeamento pode levar a uma disputa de dados e rasgo de leitura, o que significa que o valor lido não precisa mais estar relacionado ao valor antigo ou novo. Dependendo do tipo que é lido e como é usado, isso também pode levar a um comportamento indefinido com efeitos possivelmente exploráveis ​​
  • Acessar campos de união s pode permitir que você interprete dados como tipos que não representam uma instância válida ou observe dados não inicializados (se os tipos diferirem em comprimento, um campo pode incluir o preenchimento de outro), os quais levam a um comportamento indefinido e possivelmente a efeitos exploráveis ​​

Portanto, embora o código inseguro não seja exatamente o monstro temível que alguns dizem que é, é preciso ter cuidado para manipulá-lo com segurança. Então você pode escrever bases seguras em código inseguro.

Mito 4: Contanto que ninguém possa acidentalmente chamar meu código seguro com argumentos que façam com que o código inseguro abaixo falhe, estou bem

Não. Depois de apresentar uma interface segura em cima de um código inseguro, seu código mantém as invariáveis ​​de segurança, não importa o quê, ou seu código não é sólido.

Algumas pessoas têm uma opinião muito forte sobre a doença, não há razão para se irritar. Ainda é um bug e você deve abordá-lo aberta e calmamente. Se o bug pode ser resolvido com um design mais cuidadoso, vá em frente. Nesse ínterim, você pode declarar abertamente que seu código não é sólido e que os usuários precisam tomar cuidado extra para não violar as invariáveis ​​de segurança. E se você criar um design de som, arranque todas as versões não sólidas publicadas e relate a vulnerabilidade .

Mito 5: tentei executá-lo e funciona bem

O problema com o comportamento indefinido não é que ele falhará diretamente. Na verdade, pode nunca falhar. Ele também pode funcionar até você colocar o código em produção, momento em que pode falhar catastroficamente. Ou pode funcionar até que um hacker faça uma tentativa e crie apenas a entrada correta para quebrar seu código incorreto. Agora, todos os seus usuários têm um cavalo de Troia de criptoextorção em seus PCs.

Mesmo executá-lo várias vezes não terá garantia de que funcionará na próxima vez. Como mostra o desastre do ônibus espacial Columbia, só porque funcionou 135 vezes não significa que não possa falhar na 136ª tentativa.

Mas e se isso acontecer apenas em caso de vazamento?

O vazamento não pode ser evitado de forma confiável e não representa, por si só, nenhum perigo para a segurança da memória-embora o sistema operacional possa interromper seu programa ou simplesmente travar se você exaurir a memória disponível, na pior das hipóteses isso levará à negação de serviço. Portanto, ele foi considerado fora do escopo das garantias de segurança de memória e mem:: esquecer tornou-se uma função segura. Portanto, se o seu código depende de algum valor que não está vazando para segurança, em algum momento esse vazamento pode simplesmente acontecer e a perda das garantias de segurança é sua.

Lembre-se, esse mito é tão comum que demorou até um pouco antes do Rust 1.0 para finalmente permitir o vazamento de código seguro. A solução para essa fonte de insegurança é geralmente a amplificação do vazamento-vazando todo o estado corrompido possivelmente observável que pode resultar de um vazamento antes de tentar a ação insegura e recompor tudo depois. Dessa forma, um vazamento se tornará maior, às vezes muito maior, mas pelo menos não pode subverter a segurança da memória.

OK, entendi. Uma vez que uma linha de código não seguro está em minha caixa, todos os outros códigos devem ter cuidado para não quebrar as regras de segurança

O limite comumente aceito é o módulo, do qual a caixa é um tipo especial. Portanto, o curso de ação usual é criar o código inseguro em um módulo. Isso geralmente não deve ser usado externamente, mas às vezes pode ser público porque as pessoas podem usar métodos não seguros em seu código se quiserem assumir a responsabilidade em troca de desempenho (ou outras coisas).

A próxima etapa é escrever outro módulo que apresentará uma interface segura usando o código inseguro mencionado anteriormente. Este módulo deve ser a abstração mínima para permitir todos os outros casos de uso-a funcionalidade central, se preferir. Deixe de lado todas as coisas que podem ser implementadas com base neste código seguro. Essa é a parte que precisa de auditoria cuidadosa.

Finalmente, escreva a interface real que você pretende que as pessoas usem além de sua API segura. Já que você está seguro em Rust, este código precisa de menos atenção. O compilador manterá todas as suas garantias, desde que você tenha feito um bom trabalho na implementação da interface principal.

Agora que eliminamos os mitos associados ao código Rust inseguro , temos apenas uma coisa a discutir antes de entrar no código real.

Quando não escrever código inseguro

Na maioria das vezes, o código não seguro é realmente usado na busca por desempenho. Mas como escrevi em “ Como escrever código CRaP Rust ,” você deve sempre ter uma versão lenta e segura funcionando, mesmo que apenas como um instantâneo para testar e uma linha de base para comparação.

Só porque um código inseguro às vezes pode ser mais rápido, não significa que deva ser. Meça de acordo e mantenha a versão segura se ela for tão rápida ou mais rápida.

Por exemplo, ao tentar acelerar uma das entradas do Jogo de referência como um exercício, eu queria para remover uma alocação usando uma matriz em vez de um Vec , que exigia um pouco de código inseguro para lidar com dados não inicializados. No entanto, isso acabou sendo mais lento do que a versão baseada no Vec , então abandonei o esforço. Cliff L. Biffle escreveu sobre uma experiência semelhante em “ Learn Rust the Dangerous Way .”

Com o código inseguro , você não só tem menos garantia do compilador, como o compilador também tem menos garantia de trabalho, portanto, algumas otimizações podem na verdade ser desativadas para evitar a quebra do código. Portanto, sempre meça antes de fazer a troca e mantenha a versão segura por perto.

Ok, vamos começar!

Lidar com memória não inicializada

Quando o Rust atingiu 1.0.0, ele tinha uma função insegura para obter memória não inicializada: std:: mem:: uninitialized () (há também std:: mem:: zeroed () , mas a única distinção entre os dois é que o último preenche a região de memória retornada com 0 bytes).

Isso foi amplamente considerado uma má ideia, e agora a função está obsoleta e as pessoas são aconselhadas a usar o tipo std:: mem:: MaybeUninit . A razão para os problemas de não inicializado é que o valor pode ser implicitamente drop ped, seja em pânico ou outro retorno antecipado. Por exemplo:

 deixe x=std:: mem:: uninitialized ();
this_function_may_panic ();
mem:: esquecer (x);

Se a função this_function_may_panic realmente entrar em pânico, x será descartado antes mesmo de esquecer fazer isso. No entanto, descartar um valor não inicializado é um comportamento indefinido e, como as quedas são geralmente implícitas, pode ser muito difícil evitar isso. Assim, MaybeUninit foi concebido para lidar com dados potencialmente não inicializados. O tipo nunca será descartado automaticamente (como std:: mem:: ManualmenteDrop ), é conhecido pelo compilador como sendo potencialmente não inicializado e possui várias funções para lidar com dados não inicializados de forma adequada.

Vamos recapitular. Não podemos std:: ptr:: read a partir de uma memória não inicializada. Podemos nem mesmo referenciá-lo (criar um & ou & mut para ele), uma vez que o contrato de referências requer que o valor referenciado seja uma instância válida do tipo referenciado, e dados não inicializados geralmente não são (uma isenção aqui é uma referência a um ou mais MaybeUninit<_> s, uma vez que isso expressamente não requer inicialização).

Por causa disso, também não podemos descartá-lo, pois isso criaria uma referência mutável (lembre-se, fn drop (& mut self) ). Podemos transmutá-lo para outros tipos cujo contrato permite dados não inicializados (esta ainda é a maneira canônica de criar matrizes de valores não inicializados) ou std:: ptr:: write para o ponteiro que obtemos de seu método as_mut_ptr () , ou atribua outro MaybeUninit a ele-e é isso. Observe que podemos atribuir a um MaybeUninit mesmo se não inicializado, uma vez que o tipo não cai.

Como exemplo, vamos supor que queremos criar uma matriz de valores com uma função. Ou o tipo de componente de nosso array não é Copy ou não tem inicializador const , ou o LLVM é incapaz de otimizar o armazenamento duplo por algum motivo. Então, vamos inseguros :

 use std:: mem:: {MaybeUninit, transmute}; inseguro { //primeira parte: inicializar o array. Este é um dos raros casos em que //chamar diretamente `assume_init` está OK, porque um array de //`MaybeUninit` pode conter dados não inicializados. let mut array: [MaybeUninit ; 256]= MaybeUninit:: uninit (). Assume_init (); //segunda parte: inicializa os dados. Isso é seguro porque atribuímos //para um `MaybeUninit`, que é isento de` drop`. para (i, elem) em array.iter_mut (). enumerate () { * elem=MaybeUninit:: new (calcule_elem (i)); } //terceira parte: transmuta para o array inicializado. Isso funciona porque //`MaybeUninit ` tem a garantia de ter o mesmo Layout que `T`. transmutar:: <_, [MyType; 256]> (matriz)
}

Se alguma das chamadas calcular_elem (_) falhar, todo o array de MaybeUninit s será descartado. Como MaybeUninit não elimina seu conteúdo, todos os elementos calculados até agora serão vazados.

Para evitar isso, precisamos de algumas peças móveis extras:

 use std:: mem:: {esqueça, MaybeUninit, transmute}; //primeira parte extra: precisamos de um"guarda"que elimine todos os elementos * inicializados *
//em queda
struct Guard <'a> { //uma referência mutável para acessar a matriz de array: &'a mut [MaybeUninit ; 256], //o índice até o qual todos os elementos são inicializados índice: usize,
} Implantar queda para guarda <'_> { //elimina todos os elementos que são inicializados fn drop (& mut self) { para i em 0..self.index { inseguro { std:: ptr:: drop_in_place (self.array [i].as_mut_ptr ()); } } }
} inseguro { let mut array: [MaybeUninit ; 256]= MaybeUninit:: uninit (). Assume_init (); //segunda parte extra: aqui inicializamos a guarda. Daqui em diante, //toma emprestado nosso array mutably. Todo o acesso será feito através do guarda //(porque o verificador de empréstimo não nos permite acessar `array` diretamente //enquanto é mutably emprestado). deixe mut guard=Guard {array: & mut array, index: 0}; para i em 0..256 { guard.array [guard.index]=MaybeUninit:: new (calcular_elem (i)); //atualize o índice para que `drop` inclua o elemento recém-criado. guard.index +=1; } //terceira parte extra: esqueça o guarda para evitar descartar o inicializado //elementos e também encerrar o empréstimo. esquecer (guarda); transmutar:: <_, [MyType; 256]> (matriz)
}

Se você acha que é um monte de maquinário apenas para inicializar um array, você está certo. Além disso, neste ponto, certifique-se de medir o efeito no desempenho; Não posso dizer como um Vec vai se comparar.

De qualquer forma, isso mostra a maneira canônica de lidar com dados não inicializados: identificar um invariante (“todos os elementos até o índice serem inicializados”), mantê-lo (“aumentar o índice após escrever um elemento”), e você pode colher os benefícios-neste caso, nenhum vazamento em caso de pânico.

Essa abordagem também funciona bem com estruturas de dados, embora lá o invariante seja normalmente usado de forma diferente. Por exemplo, em um Vec antes de uma operação de redimensionamento que copia a memória para um armazenamento de apoio maior, o comprimento é definido como zero e restabelecido assim que a operação terminar (amplificação do vazamento). Isso garante que uma operação drop não observe memória não inicializada ou já liberada.

Dependendo da sua estrutura de dados, as invariantes podem ser bastante misteriosas. Por exemplo, você pode usar um conjunto de bits para codificar os elementos inicializados, exigindo, portanto, um oitavo a mais de memória do que a matriz simples, mas permitindo que elementos arbitrários sejam definidos ou não definidos. Nesse caso, o invariante seria”onde um bit é definido, o valor é inicializado”. Os HashMap s de Rust basicamente fazem isso.

Transformando o imutável

As regras de Rust sobre aliasing-ou seja, quantas coisas podem ser lidas ou gravadas em um local em cada ponto no tempo-são bastante rígidas. Mas às vezes precisamos violar um pouco as regras.

Para permitir isso, Rust abençoou um tipo ( inseguro , é claro) com mutabilidade interior-você pode obter um ponteiro mutável (sem referência, é claro) de um empréstimo imutável usando o método get (& self) . Também existe um método get_mut (& mut self) que retorna um empréstimo mutável para o conteúdo.

Isso significa que o compilador assumirá que tudo o que estiver em qualquer UnsafeCell é um alias. A biblioteca padrão oferece uma série de abstrações seguras, principalmente Cell , RefCell , RwLock , Mutex e o vários tipos Atomic *. Por exemplo, AtomicBool é definido da seguinte forma (anotações removidas por brevidade):

 pub struct AtomicBool { v: UnsafeCell ,
}

Obviamente, a implementação deve evitar disputas de dados, o que é feito usando operações atômicas reais via intrínsecos do LLVM. Não verifiquei o que o próximo back-end do Cranelift faz, mas parece ter algum tipo de implementação também.

Mais uma vez, antes de usar UnsafeCell diretamente, verifique se algum dos wrappers seguros funcionará para você e determine se ficar inseguro proporciona aumento de desempenho suficiente (ou outro benefícios) para valer a pena.

Motivação intrínseca (s)

A biblioteca padrão do Rust tem um conjunto de intrínsecos por tipo de CPU no módulo std:: arch . Todos eles são definidos como inseguros , principalmente porque podem não estar implementados em sua CPU. Felizmente, há uma maneira canônica de garantir que você tenha uma CPU correspondente tanto no tempo de compilação quanto no tempo de execução.

Vamos supor que você construiu seu algoritmo da maneira”normal”, olhou para a montagem e decidiu que o autovectorizador não fez um trabalho bom o suficiente por si só. É hora de lançar as grandes armas. Você basicamente escreverá assembly em Rust (porque os intrínsecos do arch são mapeados para instruções de máquina única).

Conforme declarado acima, você precisa se certificar de que o usuário possui a plataforma correta. O código a seguir mostra a maneira de verificar o suporte em tempo de compilação e tempo de execução:

//verificação de tempo de compilação
# [cfg (any (target_arch="x86", target_arch="x86_64"))]
mod simd { fn calcular ()->.. { //esta é uma macro de biblioteca std que nos permite acessar a CPU com segurança //recursos fazendo detecção de tempo de execução if is_x86_feature_detected! ("avx2") { //codifique usando intrínsecos avx2 aqui } else if is_x86_feature_detected! ("sse2") //codifique usando intrínsecos sse2 aqui } outro { //codifique sem recursos especiais, usando, por exemplo, intrínseco avx2 //seria UB! } }
} # [cfg (not (any (target_arch="x86", target_arch="x86_64")))]
mod simd { //código substituto aqui
}

Este exemplo tem apenas código especializado para x86 e x86_64 , além de vários recursos de CPU detectados em tempo de execução. Se você quiser que seu programa use intrínsecos SIMD em outras plataformas (por exemplo, ARM-NEON), será necessário adicionar outra declaração de módulo # [cfg] ‘d. Não é preciso dizer que você acabará com muito código.

Além da disponibilidade, algumas instruções também têm requisitos em torno do alinhamento . Para simplificar um pouco, o alinhamento nos diz quantos dos últimos bits de um endereço devem ser zero. Por exemplo, um valor de 32 bits pode ter um alinhamento de 4 , o que significa que os dois últimos bits de seu endereço devem ser zero. Consulte a documentação da biblioteca para obter os detalhes e o último capítulo para obter ajuda para acertar.

Montagem embutida

Digamos que você esteja escrevendo um kernel e precise de algumas coisas realmente estranhas com o ponteiro da pilha, ou outras coisas que absolutamente, positivamente exigirão montagem. Rust tem duas interfaces de idioma estrangeiro: uma para C e outra para assembly. Infelizmente, isso é instável e inseguro, então você precisará de um compilador noturno, o atributo # [feature (asm)] e um bloco inseguro . Obviamente, Rust não pode verificar o que você faz no código do assembly.

As especificações do uso de assembly embutido estão fora do escopo deste artigo. Consulte o capítulo Rust Book ou o Texto RFC . Para os fins deste artigo, a montagem embutida é apenas uma variante funky da Interface de Função Estrangeira (FFI).

Interface de funções estrangeiras

Você tem uma grande base de código C e deseja movê-la para o Rust, o que é uma tarefa difícil. Uma boa maneira de fazer isso é usar a interface de função estrangeira para mover primeiro por partes menores da base de código e depois ir módulo por módulo até que tudo esteja em Rust e você possa jogar fora o C (isto é o que librsvg vem fazendo, aliás). Ou você deseja usar o Rust do código C ++.

Em ambos os casos, você deve construir uma ponte entre o mundo seguro e mimado de Rust e o mundo duro e indiferente além. E como o mundo lá fora é perigoso, é claro que você precisa de inseguro para fazer isso.

Em primeiro lugar, certifique-se de que a interface está correta, para não se deparar com muitas horas infelizes para depurar. O bindgen (para acessar C do Rust) e cbindgen (para acessar Rust from C) utilitários são ótimos para isso.

Se você acessar o Rust a partir de C (ou C ++ por meio de uma interface C), esteja atento às vidas úteis do objeto e mantenha as vidas úteis dos objetos Rust no código Rust-ou seja, faça com que Rust elimine eles, e vice-versa para estruturas/ponteiros C. Como todos sabemos, Rust é muito específico sobre quem possui o quê e por quanto tempo ele precisa estar acessível, então documente seus requisitos.

Se, por outro lado, você envolver C (ou C ++ via extern C ) em Rust, você descobrirá que as bibliotecas C geralmente também precisam levar em consideração o tempo de vida dos dados. Depois de ter as ligações simples no lugar, tente estruturar seus tipos para levar em consideração as relações de vida útil. O livro de padrões não oficiais tem um capítulo instrutivo sobre isso.

Se você faz interface com C ++, talvez queira usar a caixa cxx . No entanto, observe que, ao contrário dos geradores de vínculos usuais, cxx não marcará suas funções como inseguras ! O argumento dos autores aqui é que o mecanismo de ligação, que é construído parcialmente em Rust e parcialmente em C ++ é seguro, e cabe a você auditar o lado C ++. Você ainda pode querer envolver a interface resultante em uma interface amigável que seja impossível de usar indevidamente de forma insegura.

Ferramentas para gravar ferrugem não segura

Como vimos, escrever Rust inseguro requer um pouco mais de cuidado do que Rust seguro, porque você não pode mais contar com o compilador para protegê-lo. Portanto, se você seguir esse caminho, é melhor levar toda a ajuda que puder obter:

Miri

Miri é o Rust MIR-esta é a representação intermediária que o Rust usa para otimizar seus programas antes de passar para o LLVM ou Cranelift-intérprete. Você pode instalá-lo via rustup com rustup component add miri . A execução é feita escrevendo cargo miri em vez de cargo simples-por exemplo, cargo miri test executará seus testes no interpretador.

Miri tem uma série de truques na manga para detectar comportamento indefinido, como acessar dados não inicializados, e dirá se algo está errado. No entanto, ele detectará apenas o comportamento indefinido em caminhos de código que são realmente executados, portanto, não pode fornecer garantia total.

Fiapos Clippy e Rust

O pacote de lint oficial do Rust tem vários lints que também podem ser úteis no código inseguro . No mínimo, o lint missing_safety_docs ajudará você a manter todos os requisitos de seus métodos inseguros documentados. Além disso, o compilador Rust não ativa todos os lints por padrão; chamar rustc-W help mostrará uma lista atualizada.

Prusti

Prusti ainda está em desenvolvimento (e atualmente tendo alguns problemas ao ser atualizado para o Rust estável mais recente, então o mais novo versão estável tem como alvo algum compilador Rust 2018), mas é uma ferramenta muito promissora que permite verificar matematicamente se algumas pós-condições sobre o código se mantêm dadas certas pré-condições.

Basicamente, você constrói uma prova de que certas invariáveis ​​sobre o seu código são mantidas, o que é ideal para abstrações seguras que precisam manter as invariáveis ​​do código inseguro. Consulte o guia do usuário para obter mais informações.

Fuzzers

O Rust Fuzz Book lista uma série de fuzzers que podem ser usados ​​com o Rust. Atualmente, ele contém seções detalhadas para usar cargo-fuzz /libfuzzer e American Fuzzy Lop / afl.rs . Ambos criarão uma grande quantidade de entradas de teste para seu código e o executarão para encontrar alguma combinação que acione uma falha.

Para detectar o uso de memória não inicializada, libdiffuzz é um alocador de memória drop-in que inicializará cada alocação de memória com valores diferentes. Executando seu código duas vezes e comparando os resultados, você pode determinar se alguma parte da memória não inicializada foi incluída no resultado. Melhor ainda, o desinfetante de memória agora tem suporte pelo menos todas as noites (o problema de rastreamento lista vários desinfetantes e seu suporte entre plataformas) e detectará qualquer leitura não inicializada, mesmo que não altere o resultado.

Embora os difusores sejam estatisticamente mais propensos a encontrar caminhos de código do que os testes de propriedade simples, não há garantia de que eles encontrarão um caminho de código específico depois de algum tempo. Eu pessoalmente tive um bug em uma função de tokenização que acabou sendo acionada por um amplo espaço em branco Unicode que encontrei dentro de um documento aleatório na internet depois de executar o fuzz de carga por uma semana com bilhões de casos de teste, mas não encontraram nada. Ainda assim, a caixa de troféus do Rust fuzz mostra um bom número de bugs que foram descobertos pelo fuzzing. Se você encontrar um, adicione-o.

rutenspitz é uma macro procedural excelente para testar o modelo de código com estado-por exemplo, estruturas de dados. Teste de modelo significa que você tem um”modelo”-ou seja, uma versão simples, mas lenta, que modela o comportamento que você deseja garantir e você usa isso para testar sua implementação insegura. Em seguida, ele irá gerar sequências de operações para testar se a relação de igualdade é mantida. Se você seguiu meu conselho acima, já deve ter uma implementação segura para testar.

A postagem Unsafe Rust: How and quando (não) usá-lo apareceu primeiro no LogRocket Blog .

Source link