Você já se perguntou como ferramentas de análise estática, como o Semgrep, conseguem identificar padrões e potenciais problemas no código sem executá-lo? A resposta está no modelo de Árvore Sintática Abstrata (AST), uma representação estruturada e hierárquica do código-fonte.
Nessa abordagem em vez de tratar o código apenas como uma sequência de caracteres, a AST organiza os elementos do código (como funções, variáveis, expressões, etc.) em nós interconectados, facilitando a análise e a manipulação.
Essa abordagem é fundamental para ferramentas de análise estática, que percorrem a AST em busca de padrões específicos, vulnerabilidades e boas práticas sem a necessidade de executar o código.
Exemplo Prático com Python
Vamos utilizar o módulo nativo ast
do Python para converter um exemplo simples de código em sua AST e, em seguida, analisar essa estrutura. Considere o seguinte código em Python:
def soma(a, b):
return a + b
resultado = soma(2, 3)
Esse código define:
- Uma função
soma(a, b)
que retorna a soma dos parâmetros. - Uma chamada à função
soma
que atribui o resultado à variávelresultado
.
Gerando a AST
Utilizando o módulo ast
, podemos transformar o código acima em uma árvore sintática:
import ast
codigo = """
def soma(a, b):
return a + b
resultado = soma(2, 3)
"""
# Parseia o código e gera a AST
arvore_ast = ast.parse(codigo)
# Exibe a AST de forma legível
print(ast.dump(arvore_ast, indent=4))
Ao executar esse script, você obterá uma saída semelhante a:
Module(
body=[
FunctionDef(
name='soma',
args=arguments(
posonlyargs=[],
args=[
arg(arg='a'),
arg(arg='b')
],
vararg=None,
kwonlyargs=[],
kw_defaults=[],
kwarg=None,
defaults=[]
),
body=[
Return(
value=BinOp(
left=Name(id='a', ctx=Load()),
op=Add(),
right=Name(id='b', ctx=Load())
)
)
],
decorator_list=[],
returns=None,
type_comment=None
),
Assign(
targets=[
Name(id='resultado', ctx=Store())
],
value=Call(
func=Name(id='soma', ctx=Load()),
args=[
Constant(value=2),
Constant(value=3)
],
keywords=[]
),
type_comment=None
)
],
type_ignores=[]
)
Analisando a saída:
- Module: Representa o módulo inteiro, ou seja, o arquivo de código.
-
FunctionDef: Define a função
soma
com seus argumentosa
eb
. -
Return: Representa a instrução de retorno dentro da função, que retorna o resultado da operação
a + b
. -
BinOp: Representa a operação binária, no caso, a soma (
Add
) entrea
eb
. -
Assign: Representa a atribuição da chamada da função
soma(2, 3)
à variávelresultado
.
Ferramentas de análise estática, como o Semgrep, percorrem a AST para identificar padrões específicos. Vamos ver um exemplo de como podemos encontrar operações de soma utilizando um visitor customizado abaixo:
class EncontrarSomas(ast.NodeVisitor):
def visit_BinOp(self, node):
if isinstance(node.op, ast.Add): # Verifica se a operação é uma soma
print(f"Encontrado: Operação de soma na linha {node.lineno}")
# Continua a visita a outros nós
self.generic_visit(node)
# Executa o analisador na AST gerada
analisador = EncontrarSomas()
analisador.visit(arvore_ast)
Ao executar este código, a saída será:
Encontrado: Operação de soma na linha 3
Isso indica que o analisador encontrou uma operação de soma na linha em que o return a + b
foi definido.
A transformação do código-fonte em uma Árvore Sintática Abstrata (AST) é uma abordagem interessante que se explorada a parte estática com a implementação dinâmica e recursos de IA, tem um potencial enorme em soluções de qualidade de código.
Com esse conhecimento, você pode começar a explorar e desenvolver suas próprias ferramentas de análise estática, seja para detectar vulnerabilidades, impor padrões de código ou otimizar seu fluxo de trabalho de desenvolvimento.
Top comments (1)
Quem tem recursos MUITO poderosos com AST? Groovy
Dá pra fazer coisas cabulosas nisto (groovy-lang.org/metaprogramming.html). E aí tá o que é lindo e horroroso ao mesmo tempo.
É lindo o poder que te dá. Mas como muitas coisas podem parecer mágica pra outros usuários do código, pode ser um pesadelo também.
Fundamental treinar a equipe.