Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • main
1 result

Target

Select target project
  • yassine.ettayeb/anastat-frama-c-24-25
  • nicolas.descamps/anastat
  • stanislas.plowiecki/anastat-frama-c-24-25
  • thomas.norodom/norodom-ana-stat-frama-c-24-25
  • quentin.vicens/ana-stat-frama-c-24-25
  • rayan.lachguel/anastat-frama-c-24-25
  • virgile.prevosto/anastat-frama-c-24-25
  • merlin.baviere/ana-stat-frama-c-24-25-merlin-baviere
8 results
Select Git revision
  • main
1 result
Show changes
"""Nodes of the abstract syntax tree of the program:
- Uop/Bop represent unary/binary arithmetic operator
- ArithExpr and its children describe arithmetic expressions
- BoolExpr and its children describe boolean expressions
- Instr and its children describe instruction
"""
from enum import Enum
from dataclasses import dataclass
class Uop(Enum):
"""unary operators. Currently only unary minus"""
OPP = 1
def __str__(self):
if self is Uop.OPP:
return "-"
return ""
class Bop(Enum):
"""binary operators: addition, multiplication, and division"""
ADD = 1
MUL = 2
DIV = 3
def __str__(self):
match self:
case Bop.ADD: return "+"
case Bop.MUL: return "*"
case Bop.DIV: return "/"
class Expr:
"""generic class for expressions, either arithmetic or boolean"""
pass
@dataclass
class ArithExpr(Expr):
"""generic class for arithmetic expressions"""
pass
@dataclass
class AECst(ArithExpr):
"""integer constants"""
value: int
def __str__(self):
return f"{self.value}"
@dataclass
class AEVar(ArithExpr):
"""variables"""
var: str
def __str__(self):
return self.var
@dataclass
class AEUop(ArithExpr):
"""unary operation"""
uop: Uop
expr: ArithExpr
def __str__(self):
return f"{self.uop}{self.expr}"
@dataclass
class AEBop(ArithExpr):
"""binary operation"""
bop: Bop
left_expr: ArithExpr
right_expr: ArithExpr
def __str__(self):
#TODO: only put parentheses if necessary
return f"({self.left_expr}) {self.bop} ({self.right_expr})"
@dataclass
class BoolExpr(Expr):
"""generic class for boolean expressions"""
pass
@dataclass
class BEPlain(BoolExpr):
"""arithmetic expression (true if not 0)"""
aexpr: ArithExpr
def __str__(self):
return f"{self.aexpr}"
@dataclass
class BEEq(BoolExpr):
"""equality between two arithmetic expression"""
left_expr: ArithExpr
right_expr:ArithExpr
def __str__(self):
return f"{self.left_expr} = {self.right_expr}"
@dataclass
class BELeq(BoolExpr):
"""less or equal"""
left_expr: ArithExpr
right_expr: ArithExpr
def __str__(self):
return f"{self.left_expr} <= {self.right_expr}"
@dataclass
class BENeg(BoolExpr):
"""Negation"""
expr: BoolExpr
def __str__(self):
return f"!{self.expr}"
class Instr:
"""generic class for instruction"""
pass
@dataclass
class ISkip(Instr):
"""Skip (do nothing)"""
def __str__(self):
return "skip"
@dataclass
class ISet(Instr):
"""assignment to a variable"""
var: str
expr: ArithExpr
def __str__(self):
return f"{self.var} := {self.expr}"
@dataclass
class ISeq(Instr):
"""sequence of two instructions"""
first: Instr
second: Instr
def __str__(self):
return f"{self.first};\n{self.second}"
@dataclass
class ICond(Instr):
"""if then else"""
cond: BoolExpr
true_branch: Instr
false_branch: Instr
def __str__(self):
return f"if {self.cond} then\n{self.true_branch}\nelse\n{self.false_branch}\nfi"
@dataclass
class ILoop(Instr):
"""while loop"""
cond: BoolExpr
body: Instr
def __str__(self):
return f"while {self.cond} do\n{self.body}\ndone"
def variables_of_aexpr(e: ArithExpr) -> frozenset[str]:
"""return the set of variables appearing in the expression"""
match e:
case AECst(_): return frozenset()
case AEVar(var): return frozenset(var)
case AEUop(expr=e): return variables_of_aexpr(e)
case AEBop(left_expr=le,right_expr=re):
return variables_of_aexpr(le) | variables_of_aexpr(re)
case _: assert False
def variables_of_bexpr(e: BoolExpr) -> frozenset[str]:
"""return the set of variable appearing in the expression"""
match e:
case BEPlain(expr): return variables_of_aexpr(expr)
case BEEq(left_expr=le,right_expr=re):
return variables_of_aexpr(le) | variables_of_aexpr(re)
case BELeq(left_expr=le,right_expr=re):
return variables_of_aexpr(le) | variables_of_aexpr(re)
case BENeg(expr):
return variables_of_bexpr(expr)
case _: assert False
def variables_of_instr(instr: Instr) -> frozenset[str]:
"""return the set of variables appearing in a program"""
match instr:
case ISkip(): return frozenset()
case ISet(var=v,expr=e):
return frozenset([v]) | variables_of_aexpr(e)
case ISeq(first=i1,second=i2):
return variables_of_instr(i1) | variables_of_instr(i2)
case ICond(cond=c,true_branch=tb,false_branch=fb):
vbranches = variables_of_instr(tb) | variables_of_instr(fb)
return variables_of_bexpr(c) | vbranches
case ILoop(cond=c,body=b):
return variables_of_bexpr(c) | variables_of_instr(b)
case _ : assert False
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1739736696,
"narHash": "sha256-zON2GNBkzsIyALlOCFiEBcIjI4w38GYOb+P+R4S8Jsw=",
"rev": "d74a2335ac9c133d6bbec9fc98d91a77f1604c1f",
"revCount": 754461,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.754461%2Brev-d74a2335ac9c133d6bbec9fc98d91a77f1604c1f/01951426-5a87-7b75-8413-1a0d9ec5ff04/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/NixOS/nixpkgs/0.1.%2A.tar.gz"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
{
description = "A Nix-flake-based Python development environment";
inputs.nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1.*.tar.gz";
outputs = { self, nixpkgs }:
let
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f {
pkgs = import nixpkgs { inherit system; };
});
in
{
devShells = forEachSupportedSystem ({ pkgs }: {
default = pkgs.mkShell {
venvDir = ".venv";
packages = with pkgs; [ python312 ] ++
(with pkgs.python312Packages; [
pip
venvShellHook
basedpyright
]);
};
});
};
}
# Analyse Statique de Programmes -- TP Analyse Statique
CentraleSupélec
Enseignant: Virgile Prevosto
## Préliminaires
Ce TP utilise exclusivement Python. On pourra utiliser l'image Docker, comme
au TP précédent, ou directement sa machine si Python y est installé.
### Rappel Docker
L'image est ici: https://github.com/Frederic-Boulanger-UPS/docker-webtop-3asl et peut être utilisée soit localement, soit depuis `MyDocker`. De plus le répertoire `config` est monté automatiquement dans le container docker si vous utilisez les scripts associé ([PowerShell](https://github.com/Frederic-Boulanger-UPS/docker-webtop-3asl/blob/main/start-3asl.ps1) pour Windows ou [sh](https://github.com/Frederic-Boulanger-UPS/docker-webtop-3asl/blob/main/start-3asl.sh) pour Linux/macOS/BSD). Ces scripts devraient automatiquement ouvrir un onglet de votre navigateur web avec une session IceWM. Si ce n'est pas le cas, vous pouvez le faire manuellement: http://localhost:3000
NB: l'interpréteur Python est `python3` et non `python`.
### Typage statique
Les fichiers proposés ont des annotations de types permettant de vérifier que
les programmes sont bien typés avant de les exécuter. On pourra utiliser
l'outil [`pyright`](https://microsoft.github.io/pyright/#/) pour la vérification. Il n'est pas présent sur l'image
Docker, mais peut s'installer via les commandes suivantes:
```sh
sudo apt update
sudo apt install pipx
sudo apt install pyright
```
Il dispose également d'un plugin vscode.
## Introduction
Le fichier [syntax.py](config/syntax.py) définit les différentes classes
syntaxique du langage While vu en cours, soit les expressions arithmétiques,
les expressions booléennes et les instructions.
## Sémantique opérationnelle
Le fichier [opsem.py](config/opsem.py) donne un squelette des fonctions à
écrire pour définir un interpréteur concret du langage While. Les fonctions
`eval_aexpr`, et `eval_bexpr` permettent d'évaluer respectivement une
expression arithmétique et une expression booléenne dans un environnement
qui associe à des noms de variable une valeur entière. En cas d'erreur
dans l'évaluation d'une (sous-)expression, on renverra None. La fonction `eval_instr`, renvoie l'environnement final obtenu en évaluant dans un environnement initial une instruction donnée. Là encore, en cas d'erreur au cours de l'évaluation, on renvoie `None`.
Compléter ces définitions, et tester le résultat à l'aide du fichier
[example_opsem.py](config/example_opsem.py), qui définit le programme de calcul de la factorielle,
et l'évalue pour `x = 4`.
## Calcul de point fixe
Le fichier [cfg.py](config/cfg.py) est donné. Il contient la définition des
différents labels des arêtes du graphe de flot de contrôle (skip, affectation,
test, et erreur). On a aussi une classe pour les nœuds, pour un graphe, et pour
un CFG proprement dit, qui ajoute au graphe les informations sur le nœud
initial, le nœud final, et le nœud d'erreur. Un CFG est construit à partir d'un
programme, et cette construction est donnée également.
Le fichier [iteration.py](config/iteration.py) contient une classe abstraite
`Transfer` qui permet d'implémenter des analyses statiques en donnant l'élément
`bottom` du treillis, l'état initial, le `join` de deux états, le test
d'inclusion, et les fonctions de transfert pour chaque étiquette.
Compléter la fonction `fixpoint_iteration` de ce fichier, qui prend en argument
une instance de `Transfer` et un CFG, et calcule le point fixe associant à
chaque nœud du CFG l'état correspondant. L'algorithme est le suivant (on ne se
préoccupe pas de terminaison):
1. le mapping initial associe l'état initial au nœud initial, et `bottom` à tous
les autres. On met toutes les arêtes qui partent du nœud initial dans la
worklist.
2. tant que la worklist n'est pas vide, on prend son premier élément, une arête
qui va du nœud `src` au nœud `dst` avec le label `l`.
3. on associe au nœud `dst` le `join` de son état actuel avec le résultat
de la fonction de transfert pour `l` appliqué à l'état associé à `src`
4. si le nouvel état est inclus dans l'état associé précédemment à `dst`, on
ne fait rien. Sinon, on ajoute les arêtes qui sortent de `dst` à la worklist
5. on revient en 2
NB: en pratique, une itération efficace sélectionne l'arête d'intérêt de manière
beaucoup plus précise, mais cela sort du cadre de ce TP.
Tester le résultat avec le fichier
[example_iteration.py](config/example_iteration.py), qui définit une analyse
statique minimaliste, qui propage un état non vide dans tous les nœuds sauf ceux
qui sont gardés par une condition qui s'évalue à `False` dans un environnement
vide. Le programme exemple a un CFG qui contient de telles arêtes. À quoi
correspondent les nœuds indiqués comme inatteignables par l'analyse?
## Propagation de constantes
Le fichier [constant_propagation.py](config/constant_propagation.py) vise à
implémenter la propagation de constantes dans le cadre défini précédemment.
Pour cela, on donne un type de valeurs abstraites qui sont soit un entier (quand
on est sûr qu'une variable est associé à cet entier), soit `None`, pour indiquer
que plusieurs valeurs sont possibles. Un environnement abstrait associe à des
variables des valeurs abstraites, et notre état sera un environnement abstrait
ou `None` pour indiquer que le nœud associé n'est jamais atteint. On donne
également les fonctions d'évaluation abstraite des expressions, dans un
environnement abstrait et qui renvoient une valeur abstraite ou `None` si une
erreur se produit.
Implémenter les différentes méthodes de la classe `Constant_propagation`. On
définira un état initial où toutes les variables utilisées dans le programme
sont associées à la valeur `abstract_value(None)`, indiquant qu'elles sont
présentes dans l'environnement mais peuvent avoir n'importe quelle valeur.
On testera le résultat à l'aide du fichier
[example_constant_propagation.py](config/example_constant_propagation.py), qui
définit un programme dont les variables reçoivent des valeurs constantes. NB:
pour que l'analyseur arrive à montrer que `c` est constante en fin de programme,
il faut raffiner la fonction de transfert du `test`, en réduisant l'état propagé
quand le test est une égalité entre une variable et une expression qui s'évalue
à une constante, pour tenir compte du fait que dans la branche correspondante,
la variable est associée à une constante. Ce raffinement est facultatif.
......@@ -38,6 +38,31 @@ int main(void) {
}
string_print(string_final);
free(string_final);
/**********************************************************************/
string_t *original = string_new("Hello world");
string_t *substring_old = string_new("world");
string_t *substring_new = string_new("Paris");
if (!original || !substring_old || !substring_new) {
printf("Invalid strings before substitution !");
return -2;
}
string_print(original);
string_t *new = string_replace(original, substring_old, substring_new);
if (!new) {
printf("Invalid strings after substitution !");
return -3;
}
free(original);
free(substring_old);
free(substring_new);
string_print(new);
free(new);
return 0;
}
......@@ -288,9 +313,14 @@ string_t *string_replace(const string_t *str, const string_t *old,
return string_clone(str);
size_t len = str->len + n * (new->len - old->len);
string_t *s = malloc(sizeof(string_t) + len);
// We add this check
if (!s) {
return s;
}
s->len = len;
char *t = s->buf;
int curr = 0;
offset = 0;
while (n--) {
......