En informatique , la programmation réflexive ou réflexion est la capacité d'un processus à examiner, introspecter et modifier sa propre structure et son comportement.
Contexte historique
Les premiers ordinateurs étaient programmés dans leurs langages d'assemblage natifs , qui étaient intrinsèquement réflexifs, car ces architectures originales pouvaient être programmées en définissant des instructions sous forme de données et en utilisant du code auto-modifiable . Alors que l'essentiel de la programmation s'est déplacé vers des langages compilés de plus haut niveau tels qu'Algol , Cobol , Fortran , Pascal et C , cette capacité réflexive a largement disparu jusqu'à l'apparition de nouveaux langages de programmation avec réflexion intégrée dans leurs systèmes de types.
La thèse de doctorat de Brian Cantwell Smith en 1982 a introduit la notion de réflexion informatique dans les langages de programmation procéduraux et la notion d' interpréteur méta-circulaire comme composant de 3-Lisp.
Utilisations
Reflection aide les programmeurs à créer des bibliothèques de logiciels génériques pour afficher des données, traiter différents formats de données, effectuer la sérialisation et la désérialisation des données pour la communication, ou effectuer le regroupement et le dégroupage des données pour les conteneurs ou les rafales de communication.
L’utilisation efficace de la réflexion nécessite presque toujours un plan : un cadre de conception, une description d’encodage, une bibliothèque d’objets, une carte d’une base de données ou des relations d’entités.
La réflexion rend un langage plus adapté au code orienté réseau. Par exemple, elle aide les langages tels que Java à bien fonctionner dans les réseaux en activant des bibliothèques pour la sérialisation, le regroupement et la variation des formats de données. Les langages sans réflexion tels que C doivent utiliser des compilateurs auxiliaires pour des tâches telles que la notation de syntaxe abstraite afin de produire du code pour la sérialisation et le regroupement.
La réflexion peut être utilisée pour observer et modifier l'exécution d'un programme au moment de l'exécution . Un composant de programme orienté réflexion peut surveiller l'exécution d'un enclos de code et peut se modifier en fonction d'un objectif souhaité de cet enclos. Cela s'effectue généralement en attribuant dynamiquement du code de programme au moment de l'exécution.
Dans les langages de programmation orientés objet tels que Java , la réflexion permet d'inspecter les classes, les interfaces, les champs et les méthodes lors de l'exécution sans connaître les noms des interfaces, des champs et des méthodes lors de la compilation . Elle permet également l'instanciation de nouveaux objets et l'invocation de méthodes.
La réflexion est souvent utilisée dans le cadre des tests logiciels , par exemple pour la création/instanciation d' objets fictifs lors de l'exécution .
La réflexion est également une stratégie clé pour la métaprogrammation .
Dans certains langages de programmation orientés objet tels que C# et Java , la réflexion peut être utilisée pour contourner les règles d'accessibilité des membres . Pour les propriétés C#, cela peut être réalisé en écrivant directement sur le champ de sauvegarde (généralement invisible) d'une propriété non publique. Il est également possible de trouver des méthodes non publiques de classes et de types et de les invoquer manuellement. Cela fonctionne pour les fichiers internes au projet ainsi que pour les bibliothèques externes telles que les assemblages de .NET et les archives de Java.
Mise en œuvre
Un langage prenant en charge la réflexion fournit un certain nombre de fonctionnalités disponibles au moment de l'exécution qui seraient autrement difficiles à réaliser dans un langage de niveau inférieur. Certaines de ces fonctionnalités sont les capacités à :
- Découvrez et modifiez les constructions de code source (telles que les blocs de code, les classes , les méthodes, les protocoles, etc.) en tant qu'objets de première classe au moment de l'exécution .
- Convertissez une chaîne correspondant au nom symbolique d'une classe ou d'une fonction en une référence ou une invocation de cette classe ou fonction.
- Évaluez une chaîne comme s’il s’agissait d’une instruction de code source au moment de l’exécution.
- Créez un nouvel interpréteur pour le bytecode du langage afin de donner une nouvelle signification ou un nouveau but à une construction de programmation.
Ces fonctionnalités peuvent être implémentées de différentes manières. Dans MOO , la réflexion fait naturellement partie du langage de programmation quotidien. Lorsque des verbes (méthodes) sont appelés, diverses variables telles que verb (le nom du verbe appelé) et this (l'objet sur lequel le verbe est appelé) sont renseignées pour donner le contexte de l'appel. La sécurité est généralement gérée en accédant à la pile d'appels par programmation : Étant donné que callers () est une liste des méthodes par lesquelles le verbe actuel a finalement été appelé, l'exécution de tests sur callers ()[0] (la commande invoquée par l'utilisateur d'origine) permet au verbe de se protéger contre toute utilisation non autorisée.
Les langages compilés s'appuient sur leur système d'exécution pour fournir des informations sur le code source. Un exécutable Objective-C compilé , par exemple, enregistre les noms de toutes les méthodes dans un bloc de l'exécutable, fournissant un tableau pour les faire correspondre aux méthodes sous-jacentes (ou aux sélecteurs de ces méthodes) compilées dans le programme. Dans un langage compilé qui prend en charge la création de fonctions à l'exécution, comme Common Lisp , l'environnement d'exécution doit inclure un compilateur ou un interpréteur.
La réflexion peut être implémentée pour les langages sans réflexion intégrée en utilisant un système de transformation de programme pour définir des modifications automatisées du code source.
Considérations de sécurité
La réflexion peut permettre à un utilisateur de créer des chemins de flux de contrôle inattendus dans une application, contournant potentiellement les mesures de sécurité. Cela peut être exploité par des attaquants. Des vulnérabilités historiques dans Java causées par une réflexion non sécurisée ont permis au code récupéré à partir de machines distantes potentiellement non fiables de sortir du mécanisme de sécurité du sandbox Java . Une étude à grande échelle de 120 vulnérabilités Java en 2013 a conclu que la réflexion non sécurisée est la vulnérabilité la plus courante dans Java, mais pas la plus exploitée.
Exemples
Les extraits de code suivants créent une instance
foo de classe
Foo et appellent sa méthode
PrintHello . Pour chaque langage de programmation , des séquences d'appel normales et basées sur la réflexion sont présentées.
Lisp commun
Voici un exemple en Common Lisp utilisant le système d'objets Common Lisp :
( defclass foo () ()) ( defmethod print-hello (( f foo )) ( format T "Bonjour de ~S~%" f )) ;; Normal, sans réflexion ( let (( foo ( make-instance 'foo ))) ( print-hello foo )) ;; Avec la réflexion pour rechercher la classe nommée "foo" et la méthode ;; nommée "print-hello" qui se spécialise sur "foo". ( let* (( foo-class ( find-class ( read-from-string "foo" ))) ( print-hello-method ( find-method ( symbol-function ( read-from-string "print-hello" )) nil ( list foo-class )))) ( funcall ( sb-mop:method-generic-function print-hello-method ) ( make-instance foo-class )))
C#
Voici un exemple en C# :
// Sans réflexion var foo = new Foo (); foo . PrintHello (); // Avec réflexion Objet foo = Activator . CreateInstance ( "complete.classpath.and.Foo" ); MethodInfo method = foo . GetType (). GetMethod ( "PrintHello" ); method . Invoke ( foo , null );
Delphi, Pascal Objet
Cet exemple Delphi et Object Pascal suppose qu'une classe TFoo a été déclarée dans une unité appelée Unit1 :
utilise RTTI , Unit1 ; procédure SansRéflexion ; var Foo : TFoo ; début Foo := TFoo . Créer ; essayer Foo . Bonjour ; enfin Foo . Libérer ; fin ; fin ; procédure WithReflection ; var RttiContext : TRttiContext ; RttiType : TRttiInstanceType ; Foo : TObject ; début RttiType := RttiContext . FindType ( 'Unit1.TFoo' ) comme TRttiInstanceType ; Foo := RttiType . GetMethod ( 'Create' ) . Invoke ( RttiType . MetaclassType , []) . AsObject ; essayer RttiType . GetMethod ( 'Hello' ) . Invoke ( Foo , []) ; finalement Foo . Free ; fin ; fin ;
CE
Voici un exemple en eC :
// Sans réflexion Foo foo { }; foo . hello (); // Avec réflexion Classe fooClass = eSystem_FindClass ( __thisModule , "Foo" ); Instance foo = eInstance_New ( fooClass ); Méthode m = eClass_FindMethod ( fooClass , "hello" , fooClass . module ); (( void ( * )())( void * ) m . function )( foo );
Aller
Voici un exemple en Go :
importer "refléter" // Sans réflexion f := Foo {} f . Hello () // Avec réflexion fT := reflect . TypeOf ( Foo {}) fV := reflect . New ( fT ) m := fV . MethodByName ( "Bonjour" ) si m . IsValid () { m . Call ( nil ) }
Java
Voici un exemple en Java :
importer java.lang.reflect.Method ; // Sans réflexion Foo foo = new Foo (); foo . hello (); // Avec réflexion , essayez { Object foo = Foo . class . getDeclaredConstructor (). newInstance (); Méthode m = foo . getClass (). getDeclaredMethod ( "bonjour" , nouvelle classe <?>[ 0 ] ); m . invoke ( foo ); } catch ( ReflectiveOperationException ignorée ) {}
JavaScript
Voici un exemple en JavaScript :
// Sans réflexion const foo = new Foo () foo . hello () // Avec réflexion const foo = Reflect . construct ( Foo ) const hello = Reflect . get ( foo , 'hello' ) Reflect . apply ( hello , foo , []) // Avec eval eval ( ' new Foo().hello()' )
Julia
Voici un exemple dans Julia :
julia> struct Point x :: Int y fin # Inspection avec réflexion julia> fieldnames ( Point ) (:x, :y) julia> types de champs ( Point ) (Int64, Tout) julia> p = Point ( 3 , 4 ) # Accès avec réflexion julia> getfield ( p , :x ) 3
Objective-C
Voici un exemple en Objective-C , impliquant l' utilisation du framework OpenStep ou Foundation Kit :
// Classe Foo. @interface Foo : NSObject - ( void ) bonjour ; @end // Envoi de « bonjour » à une instance Foo sans réflexion. Foo * obj = [[ Foo alloc ] init ]; [ obj hello ]; // Envoi de « bonjour » à une instance Foo avec réflexion. id obj = [[ NSClassFromString ( @"Foo" ) alloc ] init ]; [ obj performSelector : @selector ( hello )];
Perl
Voici un exemple en Perl :
# Sans réflexion mon $foo = Foo -> new ; $foo -> hello ; # ou Foo -> nouveau -> bonjour ; # Avec réflexion ma $class = "Foo" ma $constructor = "new" ; ma $method = "hello" ; mon $f = $classe -> $constructeur ; $f -> $méthode ; # ou $class -> $constructor -> $method ; # avec eval eval "new Foo->hello;" ;
PHP
Voici un exemple en PHP :
// Sans réflexion $foo = new Foo (); $foo -> hello (); // Avec réflexion, en utilisant l'API Reflections $reflector = new ReflectionClass ( "Foo" ); $foo = $reflector -> newInstance (); $hello = $reflector -> getMethod ( "hello" ); $hello -> invoke ( $foo );
Python
Voici un exemple en Python :
# Sans réflexion obj = Foo () obj . hello () # Avec réflexion obj = globals ()[ "Foo" ]() getattr ( obj , "hello" )() # Avec eval eval ( " Foo().hello()" )
R
Voici un exemple en R :
# Sans réflexion, en supposant que foo() renvoie un objet de type S3 qui a la méthode "hello" obj <- foo () hello ( obj ) # Avec réflexion class_name <- "foo" generic_having_foo_method <- "hello" obj <- do.call ( class_name , list ()) do.call ( generic_having_foo_method , alist ( obj ))
Rubis
Voici un exemple en Ruby :
# Sans réflexion obj = Foo . new obj . hello # Avec réflexion obj = Object . const_get ( "Foo" ) . new obj . send :hello # Avec eval eval "Foo.new.hello"
Xojo
Voici un exemple utilisant Xojo :
" Sans réflexion Dim fooInstance As New Foo fooInstance . PrintHello ' Avec réflexion Dim classInfo As Introspection . Typeinfo = GetTypeInfo ( Foo ) Dim constructors ( ) As Introspection . ConstructorInfo = classInfo . GetConstructors Dim fooInstance As Foo = constructors ( 0 ). Invoke Dim methods ( ) As Introspection . MethodInfo = classInfo . GetMethods For Each m As Introspection . MethodInfo In methods If m . Name = "PrintHello" Then m . Invoke ( fooInstance ) End If Next