Análise estática de código para sistemas complexos
O Visao lê o seu código-fonte e reporta defeitos antes de o programa correr. Encontra valores que transbordam, índices além do fim de um array e memória usada depois de libertada.
Cada ocorrência indica a regra, a linha e o caminho que leva até ela. O Visao é a parte de análise estática do Midair.
$visao compile_commands.json Using toolchain clang (clang) Parsing 1 / 3... Solving … src/rx.c:19:5: error: The index is out of range src/rx.c:26:12: error: Possible integer overflow src/rx.c:33:19: error: Dangling pointer ✓ Finished successfully · 0.4 s
O que é a análise estática de código?Verificar o código sem o executar.
A análise estática de código verifica o código-fonte sem executar o programa. Percorre todos os caminhos que o código pode seguir e reporta os pontos onde um valor, um ponteiro ou um recurso pode falhar.
Os testes respondem a outra pergunta: o que acontece nas execuções que de facto faz. Um teste vê um caminho de cada vez. Um analisador estático raciocina sobre muitos caminhos ao mesmo tempo, incluindo os raros, que nenhum teste alcança.
Por isso, os dois métodos completam-se. A análise estática encontra defeitos cedo e sem hardware. Os testes confirmam como o software se comporta no seu ambiente real.
Comparamos os dois métodos em detalhe, com exemplos: Análise estática e dinâmica de código: a comparação completa.
Ler uma ocorrência.Três marcas num ficheiro C.
O ficheiro e as ocorrências abaixo são um exemplo. Escolha uma marca na margem para ver a ocorrência e o caminho que leva até ela.
/* rx.c: receive path of a serial driver */#include <stdint.h>#include <stdlib.h>#define RX_SIZE 64struct rx_ring {uint8_t data[RX_SIZE];uint16_t head;};void log_bytes(const uint8_t *p, uint16_t n);void rx_put(struct rx_ring *r, uint8_t byte){if (r->head > RX_SIZE) {r->head = 0;}r->data[r->head] = byte;r->head++;}int32_t rx_timeout_us(const uint8_t *cfg){uint16_t ms = (uint16_t)(cfg[0] | (cfg[1] << 8));return ms * 1000 * 1000;}void rx_drop(uint8_t *buf, uint16_t len, int keep_log){free(buf);if (keep_log) {log_bytes(buf, len);}}
src/rx.c:19:5High
The index is out of range
- Regra
array/index/out_of_range- MISRA C:2012
- Rule 18.1 (Required)
- Detetada por
- Domínio de intervalos
O caminho até ela
- 9head é um contador de 16 bits e data tem 64 bytes, por isso o último índice válido é 63.
- 16A reposição só corre quando head é maior do que 64. Quando head é exatamente 64, a condição é falsa e o código continua.
- 19Nesse caminho, a escrita vai para data[64], um byte depois do fim do array.
src/rx.c:26:12High
Possible integer overflow
- Regra
arithm/integer/overflow- MISRA C:2012
- Rule 12.4 (Advisory)
- Detetada por
- Domínio de intervalos
O caminho até ela
- 25ms é construído a partir de dois bytes de configuração, por isso pode valer qualquer número entre 0 e 65535.
- 26ms é promovido a int e multiplicado por um milhão. O maior resultado é 65 535 000 000.
- 26Um int de 32 bits guarda no máximo 2 147 483 647. Qualquer ms acima de 2147 transborda.
src/rx.c:33:19Critical
Dangling pointer
- Regra
mem/ptr/dangling- MISRA C:2012
- Rule 1.3 (Required)
- Detetada por
- Domínios de ponteiros e de estado dos recursos
O caminho até ela
- 31free liberta o bloco para onde buf aponta. buf continua a guardar o endereço antigo.
- 32Nada na função fixa o valor de keep_log, por isso os dois ramos continuam possíveis.
- 33Quando keep_log está ativo, log_bytes recebe buf e lê memória que já foi libertada.
Como o Visao vê um problema.Quatro formas de raciocinar sobre um programa.
O Visao converte cada ficheiro numa única representação interna e corre várias análises sobre ela. Escolha uma para ver o que acompanha.
Intervalos
O Visao acompanha o menor e o maior valor que cada variável pode ter em cada caminho. Quando um intervalo ultrapassa o que o tipo ou o array permite, o Visao reporta-o.
Exemploms in [0, 65535]
ms * 1000 * 1000 in [0, 65 535 000 000]
int32_t max 2 147 483 647
o intervalo não cabe: overflow
Octógonos
O Visao acompanha também relações entre duas variáveis, como i < n. Muitas vezes, o intervalo de i sozinho é largo demais para decidir. A relação mostra se um acesso fica dentro do array.
Exemplofor (i = 0; i < n; i++) sum += in[i];
intervalo i in [0, 65535] não decide
octógono i - n <= -1 in[i] fica dentro de in[n]
Ponteiros
O Visao segue para onde cada ponteiro pode apontar e o estado de cada recurso: alocado, libertado, aberto, fechado. Uma leitura através de um ponteiro libertado, uma segunda libertação ou um handle que fica aberto são ocorrências.
Exemplolinha 29 buf -> bloco A válido
linha 31 free(buf) A libertado
linha 33 log_bytes(buf, len) A lido depois de libertado
SMT
Quando as análises acima respondem Unknown, o Visao pode recorrer ao solver Z3 ou CVC5. O solver decide se todas as condições de um caminho podem ser verdadeiras ao mesmo tempo. Se não podem, o caminho não existe e o Visao não reporta nada aí.
Exemplocaminho até ao acesso: x > 10 e 2 * x < 15
solver (Z3): unsat
o caminho não pode acontecer: sem ocorrência
Quando o seu código declara as suas suposições em contratos no estilo ACSL, o Visao também os verifica.
Uma ocorrência, quatro formatos.Para pessoas, para a CI e para scripts.
A mesma ocorrência de rx.c, tal como cada formato de relatório a escreve. Os exemplos estão encurtados.
A saída predefinida na consola. Cada ocorrência mostra o ficheiro, a linha e a coluna, a mensagem e a linha de código.
$ visao src/rx.csrc/rx.c:19:5: error: The index is out of range
r->data[r->head] = byte;
^
✓ Finished successfully · 0.4 sPara GitHub Code Scanning, Jenkins Warnings NG e Azure DevOps. Uma impressão digital em cada resultado mantém a ocorrência estável quando as linhas mudam de lugar.
$ visao --report-format sarif --report-output visao.sarif ...{
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"version": "2.1.0",
"runs": [{
"tool": { "driver": { "name": "Visao", "rules": [ ... ] } },
"results": [{
"ruleId": "array/index/out_of_range",
"level": "error",
"message": { "text": "The index is out of range" },
"locations": [{ "physicalLocation": {
"artifactLocation": { "uri": "src/rx.c" },
"region": { "startLine": 19, "startColumn": 5 } } }],
"partialFingerprints": { "visao/v1": "..." }
}]
}]
}Um array compacto de ocorrências para o jq e para os seus próprios scripts.
$ visao --report-format json src/rx.c{
"tool": "visao",
"findings": [{
"ruleId": "array/index/out_of_range",
"helpUri": "https://defectdb.org/array/index/out_of_range",
"severity": "High",
"level": "error",
"message": "The index is out of range",
"file": "src/rx.c",
"line": 19,
"column": 5
}]
}O esquema de relatório SAST do GitLab. O GitLab mostra-o no widget de segurança do merge request.
$ visao --report-format gitlab --report-output gl-sast-report.json ...{
"version": "15.0.6",
"scan": { "type": "sast", "scanner": { "id": "visao", "name": "Visao" } },
"vulnerabilities": [{
"id": "array/index/out_of_range:src/rx.c:19",
"category": "sast",
"name": "array/index/out_of_range",
"description": "The index is out of range",
"severity": "High",
"location": { "file": "src/rx.c", "start_line": 19 },
"identifiers": [
{ "type": "visao_rule", "value": "array/index/out_of_range" },
{ "type": "cwe", "value": "119" }
]
}]
}Bloquear só com ocorrências novas
Guarde as ocorrências do branch principal como linha de base em SARIF. Num merge request, --baseline retira todas as ocorrências que já lá estão. Depois, --fail-on high só termina com o código 1 quando uma ocorrência nova é High ou superior.
Leituras recomendadas.Guias, investigação e páginas relacionadas.
Perguntas.Respostas curtas.
O que é a análise estática de código?
A análise estática de código verifica o código-fonte sem executar o programa. Raciocina sobre todos os caminhos que o código pode seguir. Reporta defeitos como overflows, índices além do fim de um array e memória usada depois de libertada.
Em que difere dos testes?
Um teste executa o programa e vê os caminhos que correm. A análise estática vê muitos caminhos de uma vez, incluindo os raros, mas não observa o programa no seu ambiente real. A maioria das equipas usa os dois.
Que linguagens o Visao analisa?
Dezanove linguagens, incluindo C, C++, Rust, Go, Python, Java e assembly. Em C e C++ lê o seu compile_commands.json, por isso usa as mesmas flags e os mesmos caminhos de include do seu build.
O Visao verifica MISRA C?
Sim. Com --standard misra-c-2012, cada ocorrência correspondente indica o número da regra MISRA C:2012. A execução termina com um resumo das regras verificadas e não verificadas.
Como encaixa na CI?
O Visao é um único comando. Escreve SARIF 2.1.0, JSON ou um relatório SAST do GitLab, e com --fail-on o código de saída faz falhar o passo. Uma linha de base limita o bloqueio às ocorrências novas.
O nosso código sai das nossas máquinas?
Não. O Visao corre na sua máquina, não envia nada para lado nenhum e não usa serviços de terceiros.
Comece por um repositório.Veja o que o Visao encontra no seu código.
Compare os planos na página de preços. Os preços são mensais, em euros.
Entrar na lista de espera