Fonctions de bytecode non disponibles dans le langage Java

146

Y a-t-il actuellement (Java 6) des choses que vous pouvez faire dans le bytecode Java que vous ne pouvez pas faire à partir du langage Java?

Je sais que les deux sont Turing complets, alors lisez "peut faire" comme "peut faire beaucoup plus vite / mieux, ou simplement d'une manière différente".

Je pense à des bytecodes supplémentaires comme invokedynamic, qui ne peuvent pas être générés à l'aide de Java, sauf que celui-ci est spécifique pour une version future.

Bart van Heukelom
la source
3
Définissez des «choses». En fin de compte, le langage Java et le bytecode Java sont tous deux Turing complets ...
Michael Borgwardt
2
Est la vraie question; y a-t-il un avantage à programmer en byte code, par exemple en utilisant Jasmin au lieu de Java?
Peter Lawrey
2
Comme roldans l'assembleur, que vous ne pouvez pas écrire en C ++.
Martijn Courteaux le
1
C'est un très mauvais compilateur d'optimisation qui ne peut pas se compiler (x<<n)|(x>>(32-n))en rolinstruction.
Random832 du

Réponses:

62

Autant que je sache, il n'y a pas de fonctionnalités majeures dans les bytecodes pris en charge par Java 6 qui ne sont pas également accessibles à partir du code source Java. La raison principale en est évidemment que le bytecode Java a été conçu avec le langage Java à l'esprit.

Cependant, certaines fonctionnalités ne sont pas produites par les compilateurs Java modernes:

  • Le ACC_SUPERdrapeau :

    Il s'agit d'un indicateur qui peut être défini sur une classe et spécifie comment une casse d'angle spécifique du invokespecialbytecode est gérée pour cette classe. Il est défini par tous les compilateurs Java modernes (où "moderne" est> = Java 1.1, si je me souviens bien) et seuls les anciens compilateurs Java produisaient des fichiers de classe où cela n'était pas défini. Cet indicateur n'existe que pour des raisons de compatibilité descendante. Notez qu'à partir de Java 7u51, ACC_SUPER est complètement ignoré pour des raisons de sécurité.

  • Les jsr/ retbytecodes.

    Ces bytecodes ont été utilisés pour implémenter des sous-routines (principalement pour implémenter des finallyblocs). Ils ne sont plus produits depuis Java 6 . La raison de leur dépréciation est qu'ils compliquent beaucoup la vérification statique sans grand gain (c'est-à-dire que le code qui utilise peut presque toujours être réimplémenté avec des sauts normaux avec très peu de surcharge).

  • Avoir deux méthodes dans une classe qui ne diffèrent que par le type de retour.

    La spécification du langage Java n'autorise pas deux méthodes dans la même classe lorsqu'elles ne diffèrent que par leur type de retour (c'est-à-dire même nom, même liste d'arguments, ...). Cependant, la spécification JVM n'a pas une telle restriction, donc un fichier de classe peut contenir deux de ces méthodes, il n'y a tout simplement aucun moyen de produire un tel fichier de classe en utilisant le compilateur Java normal. Il y a un bel exemple / explication dans cette réponse .

Joachim Sauer
la source
5
Je pourrais ajouter une autre réponse, mais autant faire de la vôtre la réponse canonique. Vous pouvez mentionner que la signature d'une méthode dans le bytecode inclut le type de retour . Autrement dit, vous pouvez avoir deux méthodes avec exactement les mêmes types de paramètres, mais des types de retour différents. Voir cette discussion: stackoverflow.com/questions/3110014/is-this-valid-java/…
Adam Paynter
8
Vous pouvez avoir des noms de classe, de méthode et de champ avec à peu près n'importe quel caractère. J'ai travaillé sur un projet où les "champs" avaient des espaces et des tirets dans leurs noms. : P
Peter Lawrey
3
@Peter: En parlant de caractères du système de fichiers, je suis tombé sur un obfuscateur qui avait renommé une classe en aet une autre Adans le fichier JAR. Il m'a fallu environ une demi-heure de décompression sur une machine Windows avant de réaliser où se trouvaient les classes manquantes. :)
Adam Paynter
3
@JoachimSauer: spécification JVM paraphrasé page 75: noms de classe, les méthodes, les champs et les variables locales peut contenir tout caractère sauf '.', ';', '['ou '/'. Les noms de méthode sont identiques, mais ils ne peuvent pas non plus contenir '<'ni '>'. (Avec les exceptions notables des constructeurs statiques <init>et <clinit>par exemple.) Je dois souligner que si vous suivez strictement la spécification, les noms de classe sont en fait beaucoup plus contraints, mais les contraintes ne sont pas appliquées.
leviathanbadger
3
@JoachimSauer: aussi, un ajout non documenté de ma part: le langage java inclut le "throws ex1, ex2, ..., exn"dans le cadre des signatures de méthode; vous ne pouvez pas ajouter de clauses de levée d'exceptions aux méthodes remplacées. MAIS, la JVM s'en fiche. Ainsi, seules les finalméthodes sont vraiment garanties par la machine virtuelle Java comme étant sans exception - à part RuntimeExceptions et Errors, bien sûr. Voilà pour la gestion des exceptions vérifiée: D
leviathanbadger
401

Après avoir travaillé avec le code octet Java pendant un certain temps et effectué des recherches supplémentaires à ce sujet, voici un résumé de mes résultats:

Exécuter du code dans un constructeur avant d'appeler un super constructeur ou un constructeur auxiliaire

Dans le langage de programmation Java (JPL), la première instruction d'un constructeur doit être une invocation d'un super constructeur ou d'un autre constructeur de la même classe. Ce n'est pas le cas pour le code d'octet Java (JBC). Dans le code d'octet, il est absolument légitime d'exécuter n'importe quel code avant un constructeur, à condition que:

  • Un autre constructeur compatible est appelé à un moment donné après ce bloc de code.
  • Cet appel ne fait pas partie d'une instruction conditionnelle.
  • Avant cet appel de constructeur, aucun champ de l'instance construite n'est lu et aucune de ses méthodes n'est appelée. Cela implique l'élément suivant.

Définissez les champs d'instance avant d'appeler un super constructeur ou un constructeur auxiliaire

Comme mentionné précédemment, il est parfaitement légal de définir une valeur de champ d'une instance avant d'appeler un autre constructeur. Il existe même un hack hérité qui permet d'exploiter cette "fonctionnalité" dans les versions Java antérieures à 6:

class Foo {
  public String s;
  public Foo() {
    System.out.println(s);
  }
}

class Bar extends Foo {
  public Bar() {
    this(s = "Hello World!");
  }
  private Bar(String helper) {
    super();
  }
}

De cette façon, un champ pourrait être défini avant que le super constructeur ne soit appelé, ce qui n'est cependant plus possible. Dans JBC, ce comportement peut toujours être implémenté.

Branche un appel de super constructeur

En Java, il n'est pas possible de définir un appel constructeur comme

class Foo {
  Foo() { }
  Foo(Void v) { }
}

class Bar() {
  if(System.currentTimeMillis() % 2 == 0) {
    super();
  } else {
    super(null);
  }
}

Jusqu'à Java 7u23, le vérificateur de HotSpot VM a cependant manqué ce contrôle, c'est pourquoi il était possible. Cela a été utilisé par plusieurs outils de génération de code comme une sorte de hack, mais il n'est plus légal d'implémenter une classe comme celle-ci.

Ce dernier n'était qu'un bogue dans cette version du compilateur. Dans les versions plus récentes du compilateur, c'est encore possible.

Définir une classe sans aucun constructeur

Le compilateur Java implémentera toujours au moins un constructeur pour n'importe quelle classe. Dans le code d'octet Java, cela n'est pas obligatoire. Cela permet la création de classes qui ne peuvent pas être construites même en utilisant la réflexion. Cependant, l'utilisation sun.misc.Unsafepermet toujours de créer de telles instances.

Définir des méthodes avec une signature identique mais avec un type de retour différent

Dans le JPL, une méthode est identifiée comme unique par son nom et ses types de paramètres bruts. Dans JBC, le type de retour brut est également pris en compte.

Définissez des champs qui ne diffèrent pas par leur nom mais uniquement par leur type

Un fichier de classe peut contenir plusieurs champs du même nom tant qu'ils déclarent un type de champ différent. La machine virtuelle Java fait toujours référence à un champ comme un tuple de nom et de type.

Lancer des exceptions vérifiées non déclarées sans les attraper

Le runtime Java et le code d'octet Java ne connaissent pas le concept d'exceptions vérifiées. Seul le compilateur Java vérifie que les exceptions vérifiées sont toujours soit interceptées, soit déclarées si elles sont levées.

Utiliser l'appel de méthode dynamique en dehors des expressions lambda

L'appel de méthode dite dynamique peut être utilisé pour n'importe quoi, pas seulement pour les expressions lambda de Java. L'utilisation de cette fonctionnalité permet par exemple de désactiver la logique d'exécution lors de l'exécution. De nombreux langages de programmation dynamiques qui se résument à JBC ont amélioré leurs performances en utilisant cette instruction. Dans le code d'octet Java, vous pouvez également émuler des expressions lambda dans Java 7 où le compilateur n'a pas encore autorisé l'utilisation de l'invocation de méthode dynamique alors que la JVM comprenait déjà l'instruction.

Utilisez des identifiants qui ne sont normalement pas considérés comme légaux

Avez-vous déjà eu envie d'utiliser des espaces et un saut de ligne dans le nom de votre méthode? Créez votre propre JBC et bonne chance pour la révision du code. Les seuls caractères illégaux pour les identifiants sont ., ;, [et /. En outre, les méthodes qui ne sont pas nommées <init>ou <clinit>ne peuvent pas contenir <et >.

Réaffecter les finalparamètres ou la thisréférence

finalles paramètres n'existent pas dans JBC et peuvent par conséquent être réaffectés. Tout paramètre, y compris la thisréférence, n'est stocké que dans un simple tableau au sein de la JVM, ce qui permet de réaffecter la thisréférence à l'index 0dans une seule trame de méthode.

Réaffecter les finalchamps

Tant qu'un champ final est assigné dans un constructeur, il est légal de réaffecter cette valeur ou même de ne pas attribuer de valeur du tout. Par conséquent, les deux constructeurs suivants sont légaux:

class Foo {
  final int bar;
  Foo() { } // bar == 0
  Foo(Void v) { // bar == 2
    bar = 1;
    bar = 2;
  }
}

Pour les static finalchamps, il est même permis de réaffecter les champs en dehors de l'initialiseur de classe.

Traitez les constructeurs et l'initialiseur de classe comme s'il s'agissait de méthodes

C'est plus une fonctionnalité conceptuelle, mais les constructeurs ne sont pas traités différemment dans JBC que les méthodes normales. Seul le vérificateur de la JVM garantit que les constructeurs appellent un autre constructeur légal. En dehors de cela, c'est simplement une convention de dénomination Java que les constructeurs doivent être appelés <init>et que l'initialiseur de classe est appelé <clinit>. Outre cette différence, la représentation des méthodes et des constructeurs est identique. Comme Holger l'a souligné dans un commentaire, vous pouvez même définir des constructeurs avec des types de retour autres que voidou un initialiseur de classe avec des arguments, même s'il n'est pas possible d'appeler ces méthodes.

Créez des enregistrements asymétriques * .

Lors de la création d'un enregistrement

record Foo(Object bar) { }

javac générera un fichier de classe avec un seul champ nommé bar, une méthode d'accesseur nommée bar()et un constructeur en prenant un seul Object. En outre, un attribut d'enregistrement pour barest ajouté. En générant manuellement un enregistrement, il est possible de créer, une forme de constructeur différente, de sauter le champ et d'implémenter l'accesseur différemment. Dans le même temps, il est toujours possible de faire croire à l'API de réflexion que la classe représente un enregistrement réel.

Appelez n'importe quelle super méthode (jusqu'à Java 1.1)

Cependant, cela n'est possible que pour les versions Java 1 et 1.1. Dans JBC, les méthodes sont toujours distribuées sur un type de cible explicite. Cela signifie que pour

class Foo {
  void baz() { System.out.println("Foo"); }
}

class Bar extends Foo {
  @Override
  void baz() { System.out.println("Bar"); }
}

class Qux extends Bar {
  @Override
  void baz() { System.out.println("Qux"); }
}

il était possible de mettre en œuvre Qux#bazpour invoquer Foo#bazen sautant Bar#baz. S'il est toujours possible de définir un appel explicite pour appeler une autre implémentation de super méthode que celle de la super classe directe, cela n'a plus d'effet dans les versions Java après 1.1. Dans Java 1.1, ce comportement était contrôlé en définissant l' ACC_SUPERindicateur qui activerait le même comportement qui n'appelle que l'implémentation de la super classe directe.

Définir un appel non virtuel d'une méthode déclarée dans la même classe

En Java, il n'est pas possible de définir une classe

class Foo {
  void foo() {
    bar();
  }
  void bar() { }
}

class Bar extends Foo {
  @Override void bar() {
    throw new RuntimeException();
  }
}

Le code ci-dessus entraînera toujours un appel RuntimeExceptionquand foosur une instance de Bar. Il n'est pas possible de définir la Foo::foométhode pour invoquer sa propre bar méthode qui est définie dans Foo. Comme barc'est une méthode d'instance non privée, l'appel est toujours virtuel. Avec le byte code, on peut cependant définir l'invocation pour utiliser l' INVOKESPECIALopcode qui relie directement l' barappel de méthode Foo::fooà Foola version de. Cet opcode est normalement utilisé pour implémenter des invocations de super méthode, mais vous pouvez réutiliser l'opcode pour implémenter le comportement décrit.

Annotations de type à grain fin

En Java, les annotations sont appliquées en fonction de ce @Targetque les annotations déclarent. En utilisant la manipulation de code d'octet, il est possible de définir des annotations indépendamment de ce contrôle. Aussi, il est par exemple possible d'annoter un type de paramètre sans annoter le paramètre même si l' @Targetannotation s'applique aux deux éléments.

Définir n'importe quel attribut pour un type ou ses membres

Dans le langage Java, il est uniquement possible de définir des annotations pour les champs, méthodes ou classes. Dans JBC, vous pouvez essentiellement intégrer n'importe quelle information dans les classes Java. Pour utiliser ces informations, vous ne pouvez cependant plus vous fier au mécanisme de chargement de classe Java mais vous devez extraire les méta-informations vous-même.

Débordement et implicitement Assigner byte, short, charet les booleanvaleurs

Ces derniers types primitifs ne sont normalement pas connus dans JBC mais ne sont définis que pour les types de tableaux ou pour les descripteurs de champ et de méthode. Dans les instructions de code octet, tous les types nommés prennent l'espace de 32 bits ce qui permet de les représenter comme int. Officiellement, seuls les int, float, longet doubletypes existent dans le code octet qui tous besoin de conversion explicite par la règle du vérificateur de la machine virtuelle Java.

Ne pas libérer un moniteur

Un synchronizedbloc est en fait composé de deux instructions, une pour acquérir et une pour libérer un moniteur. Dans JBC, vous pouvez en acquérir un sans le relâcher.

Remarque : dans les implémentations récentes de HotSpot, cela conduit à la place à un IllegalMonitorStateExceptionà la fin d'une méthode ou à une version implicite si la méthode se termine par une exception elle-même.

Ajouter plus d'une returninstruction à un initialiseur de type

En Java, même un initialiseur de type trivial tel que

class Foo {
  static {
    return;
  }
}

est illégal. Dans le code octet, l'initialiseur de type est traité comme n'importe quelle autre méthode, c'est-à-dire que les instructions de retour peuvent être définies n'importe où.

Créer des boucles irréductibles

Le compilateur Java convertit les boucles en instructions goto dans le code d'octet Java. De telles instructions peuvent être utilisées pour créer des boucles irréductibles, ce que le compilateur Java ne fait jamais.

Définir un bloc catch récursif

Dans le code d'octet Java, vous pouvez définir un bloc:

try {
  throw new Exception();
} catch (Exception e) {
  <goto on exception>
  throw Exception();
}

Une instruction similaire est créée implicitement lors de l'utilisation d'un synchronizedbloc en Java où toute exception lors de la libération d'un moniteur renvoie à l'instruction de libération de ce moniteur. Normalement, aucune exception ne devrait se produire sur une telle instruction, mais si elle le faisait (par exemple, la version obsolète ThreadDeath), le moniteur serait toujours libéré.

Appelez n'importe quelle méthode par défaut

Le compilateur Java requiert que plusieurs conditions soient remplies afin de permettre l'invocation d'une méthode par défaut:

  1. La méthode doit être la plus spécifique (ne doit pas être remplacée par une sous-interface implémentée par n'importe quel type, y compris les super types).
  2. Le type d'interface de la méthode par défaut doit être implémenté directement par la classe qui appelle la méthode par défaut. Cependant, si l'interface Bétend l'interface Amais ne remplace pas une méthode dans A, la méthode peut toujours être appelée.

Pour le code d'octet Java, seule la deuxième condition compte. Le premier n'est cependant pas pertinent.

Invoquer une super méthode sur une instance qui ne l'est pas this

Le compilateur Java permet uniquement d'appeler une méthode super (ou par défaut d'interface) sur les instances de this. En byte code, il est cependant également possible d'appeler la super méthode sur une instance du même type similaire à la suivante:

class Foo {
  void m(Foo f) {
    f.super.toString(); // calls Object::toString
  }
  public String toString() {
    return "foo";
  }
}

Accéder aux membres synthétiques

Dans le code octet Java, il est possible d'accéder directement aux membres synthétiques. Par exemple, considérez comment dans l'exemple suivant l' Baraccès à l' instance externe d'une autre instance:

class Foo {
  class Bar { 
    void bar(Bar bar) {
      Foo foo = bar.Foo.this;
    }
  }
}

Cela est généralement vrai pour tout champ, classe ou méthode synthétique.

Définir les informations de type générique désynchronisées

Bien que l'environnement d'exécution Java ne traite pas les types génériques (après que le compilateur Java a appliqué l'effacement de type), ces informations sont toujours attachées à une classe compilée en tant que méta-informations et rendues accessibles via l'API de réflexion.

Le vérificateur ne vérifie pas la cohérence de ces Stringvaleurs codées par métadonnées. Il est donc possible de définir des informations sur des types génériques qui ne correspondent pas à l'effacement. Par principe, les affirmations suivantes peuvent être vraies:

Method method = ...
assertTrue(method.getParameterTypes() != method.getGenericParameterTypes());

Field field = ...
assertTrue(field.getFieldType() == String.class);
assertTrue(field.getGenericFieldType() == Integer.class);

En outre, la signature peut être définie comme non valide de sorte qu'une exception d'exécution est levée. Cette exception est levée lorsque les informations sont accédées pour la première fois car elles sont évaluées paresseusement. (Similaire aux valeurs d'annotation avec une erreur.)

Ajouter des méta-informations de paramètre uniquement pour certaines méthodes

Le compilateur Java permet d'incorporer le nom du paramètre et les informations de modificateur lors de la compilation d'une classe avec l' parameterindicateur activé. Dans le format de fichier de classe Java, ces informations sont cependant stockées par méthode ce qui permet de n'incorporer ces informations de méthode que pour certaines méthodes.

Gâcher les choses et planter durement votre JVM

Par exemple, dans le code d'octet Java, vous pouvez définir pour appeler n'importe quelle méthode sur n'importe quel type. Habituellement, le vérificateur se plaindra si un type ne connaît pas une telle méthode. Cependant, si vous invoquez une méthode inconnue sur un tableau, j'ai trouvé un bogue dans une version de JVM où le vérificateur manquera cela et votre JVM se terminera une fois l'instruction invoquée. Ce n'est cependant pas une fonctionnalité, mais c'est techniquement quelque chose qui n'est pas possible avec Java compilé javac . Java a une sorte de double validation. La première validation est appliquée par le compilateur Java, la seconde par la JVM lorsqu'une classe est chargée. En ignorant le compilateur, vous pourriez trouver un point faible dans la validation du vérificateur. Il s'agit cependant d'une déclaration générale plutôt que d'une fonctionnalité.

Annoter le type de récepteur d'un constructeur lorsqu'il n'y a pas de classe externe

Depuis Java 8, les méthodes non statiques et les constructeurs de classes internes peuvent déclarer un type de récepteur et annoter ces types. Les constructeurs de classes de niveau supérieur ne peuvent pas annoter leur type de récepteur car ils n'en déclarent généralement pas un.

class Foo {
  class Bar {
    Bar(@TypeAnnotation Foo Foo.this) { }
  }
  Foo() { } // Must not declare a receiver type
}

Comme Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()il renvoie cependant une AnnotatedTypereprésentation Foo, il est possible d'inclure des annotations de type pour Foole constructeur de s directement dans le fichier de classe où ces annotations sont lues ultérieurement par l'API de réflexion.

Utiliser des instructions de code d'octet inutilisées / héritées

Puisque d'autres l'ont nommé, je l'inclurai également. Java utilisait autrefois des sous-programmes par les instructions JSRet RET. JBC connaissait même son propre type d'adresse de retour à cette fin. Cependant, l'utilisation de sous-programmes compliquait trop l'analyse de code statique, raison pour laquelle ces instructions ne sont plus utilisées. Au lieu de cela, le compilateur Java dupliquera le code qu'il compile. Cependant, cela crée fondamentalement une logique identique, c'est pourquoi je ne considère pas vraiment qu'il y ait quelque chose de différent. De même, vous pouvez par exemple ajouter leNOOPInstruction de code d'octet qui n'est pas non plus utilisée par le compilateur Java mais qui ne vous permettrait pas vraiment de réaliser quelque chose de nouveau non plus. Comme indiqué dans le contexte, ces "instructions de fonctionnalités" mentionnées sont désormais supprimées de l'ensemble des opcodes légaux, ce qui les rend encore moins d'une fonctionnalité.

Rafael Winterhalter
la source
3
En ce qui concerne les noms de méthodes, vous pouvez avoir plus d'une <clinit>méthode en définissant des méthodes avec le nom <clinit>mais en acceptant des paramètres ou en ayant un voidtype de non- retour. Mais ces méthodes ne sont pas très utiles, la JVM les ignorera et le byte code ne pourra pas les invoquer. La seule utilité serait de confondre les lecteurs.
Holger
2
Je viens de découvrir que la JVM d'Oracle détecte un moniteur inédit à la sortie de la méthode et lance un IllegalMonitorStateExceptionsi vous avez omis l' monitorexitinstruction. Et en cas de sortie de méthode exceptionnelle qui n'a pas réussi à faire un monitorexit, il réinitialise le moniteur en silence.
Holger
1
@Holger - je ne le savais pas, je sais que cela était possible dans les JVM précédentes au moins, JRockit a même son propre gestionnaire pour ce type d'implémentation. Je mettrai à jour l'entrée.
Rafael Winterhalter
1
Eh bien, la spécification JVM n'impose pas un tel comportement. Je viens de le découvrir parce que j'ai essayé de créer un verrou intrinsèque suspendu en utilisant un tel code d'octet non standard.
Holger
3
Ok, j'ai trouvé la spécification pertinente : « Le verrouillage structuré est la situation où, lors d'un appel de méthode, chaque sortie sur un moniteur donné correspond à une entrée précédente sur ce moniteur. Comme il n'y a aucune garantie que tout le code soumis à la machine virtuelle Java effectuera un verrouillage structuré, les implémentations de la machine virtuelle Java sont autorisées, mais pas obligées, d'appliquer les deux règles suivantes garantissant un verrouillage structuré. … »
Holger
14

Voici quelques fonctionnalités qui peuvent être effectuées dans le bytecode Java mais pas dans le code source Java:

  • Lancer une exception vérifiée à partir d'une méthode sans déclarer que la méthode la lève. Les exceptions vérifiées et non vérifiées sont une chose qui n'est vérifiée que par le compilateur Java, pas par la JVM. Pour cette raison, par exemple, Scala peut lever des exceptions vérifiées à partir de méthodes sans les déclarer. Bien qu'avec les génériques Java, il existe une solution de contournement appelée lancer sournois .

  • Avoir deux méthodes dans une classe qui ne diffèrent que par le type de retour, comme déjà mentionné dans la réponse de Joachim : La spécification du langage Java n'autorise pas deux méthodes dans la même classe lorsqu'elles diffèrent uniquement par leur type de retour (c'est-à-dire même nom, même liste d'arguments, ...). Cependant, la spécification JVM n'a pas une telle restriction, donc un fichier de classe peut contenir deux de ces méthodes, il n'y a tout simplement aucun moyen de produire un tel fichier de classe en utilisant le compilateur Java normal. Il y a un bel exemple / explication dans cette réponse .

Esko Luontola
la source
4
Notez qu'il ya est une façon de faire la première chose en Java. Cela s'appelle parfois un lancer sournois .
Joachim Sauer le
Maintenant c'est sournois! : D Merci pour le partage.
Esko Luontola le
Je pense que vous pouvez également utiliser Thread.stop(Throwable)pour un lancer sournois. Je suppose que celui déjà lié est plus rapide.
Bart van Heukelom le
2
Vous ne pouvez pas créer une instance sans appeler un constructeur en bytecode Java. Le vérificateur rejettera tout code qui essaie d'utiliser une instance non initialisée. L'implémentation de désérialisation d'objet utilise des assistants de code natifs pour créer des instances sans appel de constructeur.
Holger
Pour une classe Foo étendant Object, vous ne pouvez pas instancier Foo en appelant un constructeur déclaré dans Object. Le vérificateur le refuserait. Vous pouvez créer un tel constructeur en utilisant ReflectionFactory de Java, mais ce n'est guère une fonctionnalité de code d'octet mais réalisée par Jni. Votre réponse est fausse et Holger a raison.
Rafael Winterhalter
8
  • GOTOpeut être utilisé avec des étiquettes pour créer vos propres structures de contrôle (autres que for whileetc.)
  • Vous pouvez remplacer la thisvariable locale dans une méthode
  • En combinant les deux, vous pouvez créer un bytecode optimisé pour un appel de queue (je le fais dans JCompilo )

Comme point connexe, vous pouvez obtenir le nom du paramètre pour les méthodes si compilées avec debug ( Paranamer le fait en lisant le bytecode

Daniel Worthington-Bodart
la source
Comment définissez-vous overridecette variable locale?
Michael
2
@Michael écraser est un mot trop fort. Au niveau du bytecode, toutes les variables locales sont accessibles par un index numérique et il n'y a pas de différence entre l'écriture dans une variable existante ou l'initialisation d'une nouvelle variable (avec une portée disjointe), dans les deux cas, il s'agit simplement d'une écriture dans une variable locale. La thisvariable a un index zéro, mais en plus d'être pré-initialisée avec la thisréférence lors de la saisie d'une méthode d'instance, il s'agit simplement d'une variable locale. Ainsi, vous pouvez y écrire une valeur différente, qui peut agir comme une fin thisde portée ou une modification de la thisvariable, selon la façon dont vous l'utilisez.
Holger
Je vois! Alors vraiment, est-ce que cela thispeut être réaffecté? Je pense que c'était juste le mot override qui m'a amené à me demander ce que cela signifiait exactement.
Michael
5

Peut-être que la section 7A de ce document est intéressante, bien qu'elle concerne les pièges du bytecode plutôt que les fonctionnalités du bytecode .

Eljenso
la source
Lecture intéressante, mais il ne semble pas que l'on veuille (ab) utiliser l'une de ces choses.
Bart van Heukelom le
4

En langage Java, la première instruction d'un constructeur doit être un appel au constructeur de la super classe. Bytecode n'a pas cette limitation, à la place la règle est que le constructeur de super classe ou un autre constructeur de la même classe doit être appelé pour l'objet avant d'accéder aux membres. Cela devrait permettre plus de liberté comme:

  • Créez une instance d'un autre objet, stockez-la dans une variable locale (ou pile) et transmettez-la en tant que paramètre au constructeur de la super classe tout en conservant la référence dans cette variable pour une autre utilisation.
  • Appelez différents autres constructeurs en fonction d'une condition. Cela devrait être possible: Comment appeler un constructeur différent de manière conditionnelle en Java?

Je ne les ai pas testés, veuillez donc me corriger si je me trompe.

msell
la source
Vous pouvez même définir les membres d'une instance avant d'appeler son constructeur de superclasse. La lecture de champs ou d'appels de méthodes n'est cependant pas possible avant cela.
Rafael Winterhalter
3

Quelque chose que vous pouvez faire avec du code octet, plutôt que du code Java simple, est de générer du code qui peut être chargé et exécuté sans compilateur. De nombreux systèmes ont JRE plutôt que JDK et si vous voulez générer du code dynamiquement, il peut être préférable, sinon plus facile, de générer du code d'octet au lieu du code Java doit être compilé avant de pouvoir être utilisé.

Peter Lawrey
la source
6
Mais alors vous sautez simplement le compilateur, vous ne produisez pas quelque chose qui ne pourrait pas être produit à l'aide du compilateur (s'il était disponible).
Bart van Heukelom le
2

J'ai écrit un optimiseur de bytecode quand j'étais un I-Play, (il a été conçu pour réduire la taille du code pour les applications J2ME). Une fonctionnalité que j'ai ajoutée était la possibilité d'utiliser le bytecode en ligne (similaire au langage d'assemblage en ligne en C ++). J'ai réussi à réduire la taille d'une fonction qui faisait partie d'une méthode de bibliothèque en utilisant l'instruction DUP, car j'ai besoin de la valeur deux fois. J'avais également des instructions de zéro octet (si vous appelez une méthode qui prend un caractère et que vous voulez passer un int, dont vous savez qu'il n'a pas besoin d'être casté, j'ai ajouté int2char (var) pour remplacer char (var) et cela supprimerait l'instruction i2c pour réduire la taille du code. Je lui ai également fait faire float a = 2.3; float b = 3.4; float c = a + b; et cela serait converti en virgule fixe (plus rapide, et certains J2ME ne l'ont pas supporte la virgule flottante).

nharding
la source
2

En Java, si vous essayez de remplacer une méthode publique avec une méthode protégée (ou toute autre réduction d'accès), vous obtenez une erreur: "tentative d'attribution de privilèges d'accès plus faibles". Si vous le faites avec le bytecode JVM, le vérificateur convient parfaitement et vous pouvez appeler ces méthodes via la classe parente comme si elles étaient publiques.

Joseph Sible-Réintégrer Monica
la source