Article de reference

sécurité de type

En informatique , la sécurité des types désigne la capacité d'un langage de programmation à prévenir les erreurs de type . Les langages à sécurité des types sont parfois qualifi...

En informatique , la sécurité des types désigne la capacité d'un langage de programmation à prévenir les erreurs de type . Les langages à sécurité des types sont parfois qualifiés de fortement ou strictement typés . Les comportements considérés comme des erreurs de type par un langage donné résultent généralement de tentatives d'opérations sur des valeurs qui ne sont pas du type approprié , par exemple, tenter d'additionner une chaîne de caractères à un entier .

L'application des types peut être statique (détection des erreurs potentielles à la compilation ), dynamique (association des informations de type aux valeurs à l'exécution et consultation de ces dernières au besoin pour détecter les erreurs imminentes) ou une combinaison des deux. L'application dynamique des types permet souvent d'exécuter des programmes qui seraient invalides avec une application statique, mais au prix de l'introduction d'erreurs à l'exécution.

Dans le contexte des systèmes de types statiques (à la compilation), la sûreté de type implique généralement (entre autres) la garantie que la valeur finale de toute expression appartiendra légitimement au type statique de cette expression. L'exigence précise est en réalité plus complexe ; voir, par exemple, le sous-typage et le polymorphisme .

Définitions

Intuitivement, la validité typographique est résumée par la formule concise de Robin Milner :

Les programmes bien typés ne peuvent pas « mal tourner ».

Autrement dit, si un système de types est correct , les expressions acceptées par ce système doivent s'évaluer à une valeur du type approprié (plutôt que de produire une valeur d'un autre type ou de provoquer une erreur de type). Vijay Saraswat propose la définition suivante :

Un langage est sûr en termes de types si les seules opérations qui peuvent être effectuées sur les données du langage sont celles autorisées par le type des données.

Cependant, la signification précise des termes « bien typé » et « erroné » pour un programme dépend des propriétés de sa sémantique statique et dynamique , spécifiques à chaque langage de programmation. Par conséquent, une définition formelle et précise de la validité des types dépend du style de sémantique formelle utilisé pour spécifier un langage. En 1994, Andrew Wright et Matthias Felleisen ont formulé ce qui est devenu la définition et la technique de preuve standard pour la sûreté des types dans les langages définis par sémantique opérationnelle , qui correspond le mieux à la notion de sûreté des types telle que la plupart des programmeurs la comprennent. Selon cette approche, la sémantique d'un langage doit posséder les deux propriétés suivantes pour être considérée comme valide :

Progrès
Un programme bien typé ne se bloque jamais : toute expression est soit déjà une valeur , soit peut être réduite à une valeur selon une méthode bien définie. Autrement dit, le programme n’atteint jamais un état indéfini où aucune transition supplémentaire n’est possible.
Préservation (ou réduction du sujet )
Après chaque étape d'évaluation, le type de chaque expression reste le même (c'est-à-dire que son type est préservé ).

Un certain nombre d'autres traitements formels de la validité des types ont également été publiés en termes de sémantique dénotationnelle et de sémantique opérationnelle structurelle .

Lien avec d'autres formes de sécurité

Prise isolément, la validité des types est une propriété relativement faible, puisqu'elle stipule essentiellement que les règles d'un système de types sont cohérentes et ne peuvent être contournées. Cependant, en pratique, les langages de programmation sont conçus de sorte qu'un bon typage implique également d'autres propriétés plus fortes, dont voici quelques exemples :

  • Prévention des opérations illégales. Par exemple, un système de types peut rejeter l'expression 3 / "Hello, World"comme invalide, car l' opérateur de division n'est pas défini pour un diviseur de chaîne .
  • Sécurité de la mémoire
    • Les systèmes de typage peuvent empêcher les pointeurs non standard qui pourraient autrement apparaître lorsqu'un pointeur vers un type d'objet est traité comme un pointeur vers un autre type.
    • Les systèmes de types plus sophistiqués, tels que ceux prenant en charge les types dépendants , peuvent détecter et rejeter les accès hors limites, empêchant ainsi les débordements de tampon potentiels .
  • Les erreurs logiques proviennent de la sémantique des différents types. Par exemple, les pouces et les millimètres peuvent tous deux être stockés comme des entiers, mais ne doivent pas être intervertis ni additionnés. Un système de types peut imposer deux types entiers différents pour ces unités.

Langages à typage statique et à typage dynamique non statique

La sûreté de type est généralement une exigence pour tout langage jouet (c.-à-d. un langage ésotérique ) proposé dans la recherche académique sur les langages de programmation. Cependant, de nombreux langages sont trop volumineux pour que des preuves de sûreté de type générées manuellement soient possibles, car elles nécessitent souvent la vérification de milliers de cas. Néanmoins, certains langages, comme Standard ML , dont la sémantique est rigoureusement définie, ont été prouvés conformes à une définition de la sûreté de type. D'autres langages, comme Haskell, sont considérés comme conformes à une certaine définition de la sûreté de type, à condition que certaines fonctionnalités d'« échappement » ne soient pas utilisées (par exemple, ` unsafePerformIO` de Haskell , utilisé pour « échapper » à l'environnement restreint habituel dans lequel les entrées/sorties (E/S) sont possibles, contourne le système de types et peut donc être utilisé pour compromettre la sûreté de type. ). Le « type punning » est un autre exemple de telle fonctionnalité d'« échappement ». Indépendamment des propriétés de la définition du langage, certaines erreurs peuvent survenir à l'exécution en raison de bogues dans l'implémentation ou dans les bibliothèques liées écrites dans d'autres langages ; de telles erreurs pourraient rendre un type d'implémentation donné non sûr dans certaines circonstances. Une version antérieure de la machine virtuelle Java de Sun était vulnérable à ce type de problème.

typage fort et faible

Les langages de programmation sont souvent classés, de manière informelle, en langages fortement typés ou faiblement typés (ou à typage lâche) en fonction de certains aspects de la sécurité des types. En 1974, Liskov et Zilles ont défini un langage fortement typé comme un langage dans lequel « lorsqu'un objet est passé d'une fonction appelante à une fonction appelée, son type doit être compatible avec le type déclaré dans la fonction appelée » . En 1977, Jackson écrivait : « Dans un langage fortement typé, chaque zone de données possède un type distinct et chaque processus exprime ses exigences de communication en fonction de ces types » . À l'inverse, un langage faiblement typé peut produire des résultats imprévisibles ou effectuer des conversions de type implicites

Gestion de la mémoire et sécurité des types

La sécurité des types est étroitement liée à la sécurité de la mémoire . Par exemple, dans une implémentation d'un langage qui possède certains types

Par définition, un langage à typage statique ne doit pas autoriser les pointeurs non initialisés entre des allocations de types différents. Cependant, la plupart des langages imposent l'utilisation correcte des types de données abstraits définis par les programmeurs, même lorsque cela n'est pas strictement nécessaire pour la sécurité de la mémoire ou la prévention de toute défaillance critique. Chaque allocation se voit attribuer un type décrivant son contenu, et ce type reste fixe pendant toute la durée de l'allocation. Cela permet à l'analyse d'alias basée sur les types de déduire que les allocations de types différents sont distinctes.

La plupart des langages à typage statique utilisent le ramasse-miettes . Pierce affirme qu'« il est extrêmement difficile d'assurer la sécurité des types en présence d'une opération de désallocation explicite », en raison du problème des pointeurs non initialisés. Cependant, Rust est généralement considéré comme sûr au niveau des types et utilise un vérificateur d'emprunt pour garantir la sécurité de la mémoire, au lieu du ramasse-miettes.

sécurité des types dans les langages orientés objet

Dans les langages orientés objet, la sécurité des types est généralement intrinsèque à la présence d'un système de types . Ceci s'exprime par des définitions de classes.

Une classe définit essentiellement la structure des objets qui en héritent, et une API sert de contrat pour la manipulation de ces objets. Chaque nouvel objet créé devra respecter ce contrat.

Chaque fonction qui échange des objets dérivés d'une classe spécifique ou implémentant une interface spécifique respectera ce contrat : par conséquent, dans cette fonction, les opérations autorisées sur cet objet seront uniquement celles définies par les méthodes de la classe que l'objet implémente. Ceci garantira la préservation de l'intégrité de l'objet.

Font exception à cette règle les langages orientés objet qui permettent la modification dynamique de la structure de l'objet, ou l'utilisation de la réflexion pour modifier le contenu d'un objet afin de surmonter les contraintes imposées par les définitions des méthodes de classe.

Problèmes de sécurité des types dans certaines langues

Ada

. L'instruction `Unchecked_Deallocation` peut être bannie d'une unité de code Ada en appliquant la directive `pragma Pure` à cette unité. Il est attendu des programmeurs qu'ils utilisent les constructions `Unchecked_` avec une grande prudence et uniquement lorsque cela est nécessaire ; les programmes qui ne les utilisent pas sont sûrs.

Le langage de programmation SPARK est un sous-ensemble d'Ada qui élimine toutes ses ambiguïtés et failles de sécurité potentielles, tout en ajoutant des contrats statiquement vérifiés aux fonctionnalités disponibles. SPARK évite les problèmes liés aux pointeurs invalides en interdisant totalement l'allocation de mémoire à l'exécution.

Ada2012 ajoute des contrats statiquement vérifiés au langage lui-même (sous forme de pré- et post-conditions, ainsi que d'invariants de type).

C

C++

Le C++ est généralement plus sûr en termes de types que son prédécesseur, le C, avec des fonctionnalités telles que l'interdiction de la conversion implicite void*vers d'autres types de pointeurs et d'autres fonctionnalités telles que :

  • Le nouvel opérateur renvoie un pointeur de type basé sur l'opérande, tandis que malloc renvoie un pointeur void.
  • Le code C++ peut utiliser des fonctions virtuelles et des modèles pour obtenir un polymorphisme sûr sans pointeurs void.
  • Des opérateurs de conversion plus sûrs, tels que dynamic_castceux qui effectuent une vérification de type à l'exécution entre les pointeurs et les références ; tandis que static_castceux qui effectuent une vérification à la compilation entre les types qui vont être convertis l'un en l'autre, ce qui est généralement plus sûr que les conversions de style C.
  • Les énumérations fortement typées de C++11 (également appelées enum class'es) ne peuvent pas être converties implicitement en ou depuis des entiers ou d'autres types d'énumération.
  • Les constructeurs explicites C++ et les opérateurs de conversion explicites C++11 empêchent les conversions de type implicites.

C#

C# est un langage sûr en termes de types. Il prend en charge les pointeurs non typés, mais leur accès nécessite l'utilisation du mot-clé « unsafe », dont l'utilisation peut être interdite au niveau du compilateur. Il intègre une validation des conversions de type à l'exécution. Ces conversions peuvent être validées à l'aide du mot-clé « as », qui renvoie une référence nulle en cas de conversion invalide, ou à l'aide d'une conversion de type de style C, qui lève une exception en cas de conversion invalide. Voir les opérateurs de conversion C# .

Une dépendance excessive au type objet (dont dérivent tous les autres types) risque de compromettre l'objectif du système de types C#. Il est généralement préférable d'abandonner les références d'objet au profit des génériques , à l'instar des modèles en C++ et des génériques en Java .

Java

La norme ML possède une sémantique rigoureusement définie et est reconnue pour sa sécurité de typage. Cependant, certaines implémentations, comme la norme ML du New Jersey (SML/NJ), sa variante syntaxique MLton , proposent des bibliothèques offrant des opérations non sécurisées. Ces fonctionnalités sont souvent utilisées conjointement avec les interfaces de fonctions externes de ces implémentations pour interagir avec du code non-ML (comme des bibliothèques C) pouvant nécessiter des données organisées d'une manière spécifique. Un autre exemple est l' interface interactive de SML/NJ elle-même, qui doit recourir à des opérations non sécurisées pour exécuter le code ML saisi par l'utilisateur.

Module-2

Modula-2 est un langage fortement typé dont la philosophie de conception exige que toute fonctionnalité non sécurisée soit explicitement signalée comme telle. Ceci est réalisé en « déplaçant » ces fonctionnalités dans une pseudo-bibliothèque intégrée appelée SYSTEM, depuis laquelle elles doivent être importées avant de pouvoir être utilisées. L'importation permet ainsi de rendre visible l'utilisation de ces fonctionnalités. Malheureusement, cette approche n'a pas été implémentée dans le rapport initial du langage ni dans son implémentation. Il subsistait des fonctionnalités non sécurisées, telles que la syntaxe de conversion de type et les enregistrements de variantes (hérités de Pascal), qui pouvaient être utilisées sans importation préalable. La difficulté liée au déplacement de ces fonctionnalités dans le pseudo-module SYSTEM résidait dans l'absence d'identifiant importable pour la fonctionnalité, car seuls les identifiants peuvent être importés, et non la syntaxe.

SYSTÈME D'IMPORTATION ; (* autorise l'utilisation de certaines installations non sécurisées : *) VAR mot : SYSTEM . MOT ; adresse : SYSTEM . ADRESSE ; adresse := SYSTEM . ADR ( mot );(* mais la syntaxe de conversion de type peut être utilisée sans une telle importation *) VAR i : INTEGER ; n : CARDINAL ; n := CARDINAL ( i ); (* ou *) i := INTEGER ( n );

La norme ISO Modula-2 a corrigé ce problème pour la fonctionnalité de conversion de type en modifiant la syntaxe de conversion de type en une fonction appelée CAST qui doit être importée depuis le pseudo-module SYSTEM. Cependant, d'autres fonctionnalités non sécurisées, telles que les enregistrements de variantes, sont restées disponibles sans aucune importation depuis le pseudo-module SYSTEM.

IMPORT SYSTEM ; VAR i : INTEGER ; n : CARDINAL ; i := SYSTEM . CAST ( INTEGER , n ); (* Conversion de type en ISO Modula-2 *)

Une révision récente du langage a appliqué rigoureusement la philosophie de conception originale. Premièrement, le pseudo-module SYSTEM a été renommé UNSAFE afin de rendre plus explicite le caractère non sécurisé des fonctionnalités importées depuis ce dernier. Ensuite, toutes les fonctionnalités non sécurisées restantes ont été soit supprimées (par exemple, les enregistrements de variantes), soit déplacées vers le pseudo-module UNSAFE. Pour les fonctionnalités ne disposant d'aucun identifiant importable, des identifiants d'activation ont été introduits. Afin d'activer une telle fonctionnalité, son identifiant d'activation correspondant doit être importé depuis le pseudo-module UNSAFE. Il ne reste plus aucune fonctionnalité non sécurisée dans le langage qui ne nécessite pas d'importation depuis UNSAFE.

IMPORT UNSAFE ; VAR i : INTEGER ; n : CARDINAL ; i := UNSAFE . CAST ( INTEGER , n ); (* Conversion de type dans Modula-2 Revision 2010 *)FROM UNSAFE IMPORT FFI ; (* identifiant d'activation pour l'interface de fonction étrangère *) <*FFI="C"*> (* pragma pour l'interface de fonction étrangère vers C *)

Pascal

type TwoTypes = enregistrement I : Entier ; Q : Réel ; fin ;DualTypes = enregistrement I : Entier ; Q : Réel ; fin ;var T1 , T2 : TwoTypes ; D1 , D2 : DualTypes ;

En cas de typage strict, une variable définie comme TwoTypes n'est pas compatible avec DualTypes (car elles ne sont pas identiques, même si les composants de ce type défini par l'utilisateur le sont) et son affectation est illégale. Une affectation serait légale car les sous-types auxquels elles sont définies sont identiques. Cependant, une affectation telle que serait légale.T1:= D2;T1:= T2;T1.Q:= D1.Q;

Common Lisp

En général, Common Lisp est un langage à typage statique. Le compilateur Common Lisp est chargé d'effectuer des vérifications dynamiques pour les opérations dont le typage statique ne peut être prouvé. Cependant, un programmeur peut indiquer qu'un programme doit être compilé avec un niveau de vérification dynamique des types inférieur. Un programme compilé dans ce mode ne peut être considéré comme à typage statique.

Exemples C++

Les exemples suivants illustrent comment les opérateurs de conversion de type en C++ peuvent compromettre la sécurité des types lorsqu'ils sont mal utilisés. Le premier exemple montre comment des types de données de base peuvent être convertis incorrectement :

#include <iostream> using namespace std ;int main () { int ival = 5 ; // valeur entière float fval = reinterpret_cast < float &> ( ival ); // réinterpréter le motif binaire cout << fval << endl ; // afficher l'entier sous forme de float return 0 ; }

Dans cet exemple, reinterpret_castle compilateur est explicitement empêché d'effectuer une conversion sécurisée d'un entier en une valeur à virgule flottante. À l'exécution, le programme affichera une valeur à virgule flottante erronée. Ce problème aurait pu être évité en écrivant plutôt :float fval = ival;

L'exemple suivant montre comment les références d'objets peuvent être incorrectement converties à une version inférieure :

#include <iostream> using namespace std ;class Parent { public : virtual ~ Parent () {} // destructeur virtuel pour RTTI };classe Enfant1 : Parent public { public : int a ; };classe Enfant2 : Parent public { public : float b ; };int main ( ) { Child1 c1 ; c1.a = 5 ; Parent & p = c1 ; // conversion ascendante toujours sûre Child2 & c2 = static_cast < Child2 & > ( p ); // conversion descendante invalide cout << c2.b << endl ; // affichera des données erronées return 0 ; }

Les deux classes enfants possèdent des membres de types différents. Lors de la conversion d'un pointeur de classe parente vers un pointeur de classe enfant, le pointeur résultant peut ne pas pointer vers un objet valide du type attendu. Dans l'exemple, cela entraîne l'affichage d'une valeur erronée. Ce problème aurait pu être évité en remplaçant la méthode static_castpar dynamic_castune méthode qui lève une exception en cas de conversion invalide.

Plus d articles de Worldlex Wiki

Revenez a l index pour explorer davantage de pages sur l histoire, la science, la culture, la geographie et la societe en francais.

Explorer l index