nodisassemble

A Clojure library designed to let you inspect bytecode of functions and things.

Artifacts

The Most Recent Release is available on clojars

With Leiningen:

 [nodisassemble "0.1.3"]

HOWEVER, don't use it this way, let lein-nodissassemble's project middleware inject it for you.

{:plugins [[lein-nodisassemble "0.1.3"]]}

Usage

no.disassemble is the runtime library created to negotiate with the agent ClassTransformer that stores class bytes globally.

In order to use no.disassemble, add lein-nodisassemble to your :plugins, which will initialize the agent transformer and make bytecode available.

WARNING: there is no cleanup of bytecode yet, so evaling a lot would surely exhaust the heap.

user=> (require 'no.disassemble)
nil

user=> (in-ns 'no.disassemble)
#<Namespace no.disassemble>

no.disassemble=> (disassemble (fn []))
"// Compiled from NO_SOURCE_FILE (version 1.5 : 49.0, super bit)\npublic final class no.disassemble$eval1170$fn__1171 extends clojure.lang.AFunction {\n  \n  // Method descriptor #7 ()V\n  // Stack: 0, Locals: 0\n  public static {};\n    0  return\n      Line numbers:\n        [pc: 0, line: 1]\n  \n  // Method descriptor #7 ()V\n  // Stack: 1, Locals: 1\n  public disassemble$eval1170$fn__1171();\n    0  aload_0\n    1  invokespecial clojure.lang.AFunction() [10]\n    4  return\n      Line numbers:\n        [pc: 0, line: 1]\n  \n  // Method descriptor #12 ()Ljava/lang/Object;\n  // Stack: 1, Locals: 1\n  public java.lang.Object invoke();\n    0  aconst_null\n    1  areturn\n      Line numbers:\n        [pc: 0, line: 1]\n      Local variable table:\n        [pc: 0, pc: 1] local: this index: 0 type: java.lang.Object\n\n}"

no.disassemble=> (println (disassemble (fn [])))
// Compiled from NO_SOURCE_FILE (version 1.5 : 49.0, super bit)
public final class no.disassemble$eval1174$fn__1175 extends clojure.lang.AFunction {
  
  // Method descriptor #7 ()V
  // Stack: 0, Locals: 0
  public static {};
    0  return
      Line numbers:
        [pc: 0, line: 1]
  
  // Method descriptor #7 ()V
  // Stack: 1, Locals: 1
  public disassemble$eval1174$fn__1175();
    0  aload_0
    1  invokespecial clojure.lang.AFunction() [10]
    4  return
      Line numbers:
        [pc: 0, line: 1]
  
  // Method descriptor #12 ()Ljava/lang/Object;
  // Stack: 1, Locals: 1
  public java.lang.Object invoke();
    0  aconst_null
    1  areturn
      Line numbers:
        [pc: 0, line: 1]
      Local variable table:
        [pc: 0, pc: 1] local: this index: 0 type: java.lang.Object

}
nil
no.disassemble=> 

You can also manipulate the disassembly as a Clojure data structure:

user=> (-> (fn []) disassemble-data pprint)
{:minor-version 0,
 :superclass-name clojure/lang/AFunction,
 :class? true,
 :major-version 49,
 :name user$eval1321$fn__1322,
 :interface-names [],
 :methods
 ({:access-flags #{:public :static},
   :name <clinit>,
   :clinit? true,
   :deprecated? false,
   :code
   {:max-locals 0,
    :max-stack 0,
    :line-numbers {0 1},
    :local-variables nil,
    :exception-table (),
    :raw-bytecode [-79],
    :bytecode [[:return 0]],
    :code-length 1},
   :descriptor "()V",
   :constructor? false,
   :exception nil,
   :synthetic? false}
  {:access-flags #{:public},
   :name <init>,
   :clinit? false,
   :deprecated? false,
   :code
   {:max-locals 1,
    :max-stack 1,
    :line-numbers {0 1},
    :local-variables nil,
    :exception-table (),
    :raw-bytecode [42, -73, 0, 10, -79],
    :bytecode
    [[:aload_0 0]
     [:invokespecial
      1
      10
      {:kind :methodref,
       :class-name "clojure/lang/AFunction",
       :method-name "<init>",
       :method-descriptor "()V"}]
     [:return 4]],
    :code-length 5},
   :descriptor "()V",
   :constructor? true,
   :exception nil,
   :synthetic? false}
  {:access-flags #{:public},
   :name invoke,
   :clinit? false,
   :deprecated? false,
   :code
   {:max-locals 1,
    :max-stack 1,
    :line-numbers {0 1},
    :local-variables
    ({:name this,
      :descriptor "Ljava/lang/Object;",
      :length 1,
      :start-pc 0}),
    :exception-table (),
    :raw-bytecode [1, -80],
    :bytecode [[:aconst_null 0] [:areturn 1]],
    :code-length 2},
   :descriptor "()Ljava/lang/Object;",
   :constructor? false,
   :exception nil,
   :synthetic? false}),
 :attributes
 ("NO_SOURCE_FILE"
  {:type org.eclipse.jdt.internal.core.util.ClassFileAttribute,
   :name "SourceDebugExtension",
   :length 94}),
 :fields (),
 :interface? false}
  • It can even disassemble itself
    no.disassemble=> (println (disassemble disassemble))
    // Compiled from disassemble.clj (version 1.5 : 49.0, super bit)
    public final class no.disassemble$disassemble extends clojure.lang.AFunction {
      
      // Field descriptor #7 Lclojure/lang/Var;
      public static final clojure.lang.Var const__0;
      
      // Field descriptor #7 Lclojure/lang/Var;
      public static final clojure.lang.Var const__1;
      
      // Field descriptor #7 Lclojure/lang/Var;
      public static final clojure.lang.Var const__2;
      
      // Field descriptor #7 Lclojure/lang/Var;
      public static final clojure.lang.Var const__3;
      
      // Field descriptor #7 Lclojure/lang/Var;
      public static final clojure.lang.Var const__4;
      
      // Field descriptor #13 Lclojure/lang/Keyword;
      public static final clojure.lang.Keyword const__5;
      
      // Field descriptor #7 Lclojure/lang/Var;
      public static final clojure.lang.Var const__6;
      
      // Field descriptor #16 Lclojure/lang/KeywordLookupSite;
      static final clojure.lang.KeywordLookupSite __site__0__;
      
      // Field descriptor #18 Lclojure/lang/ILookupThunk;
      static clojure.lang.ILookupThunk __thunk__0__;
      
      // Method descriptor #20 ()V
      // Stack: 4, Locals: 0
      public static {};
          0  ldc  [22]
          2  ldc  [24]
          4  invokestatic clojure.lang.RT.var(java.lang.String, java.lang.String) : clojure.lang.Var [30]
          7  checkcast clojure.lang.Var [32]
         10  putstatic no.disassemble$disassemble.const__0 : clojure.lang.Var [34]
         13  ldc  [36]
         15  ldc  [38]
         17  invokestatic clojure.lang.RT.var(java.lang.String, java.lang.String) : clojure.lang.Var [30]
         20  checkcast clojure.lang.Var [32]
         23  putstatic no.disassemble$disassemble.const__1 : clojure.lang.Var [40]
         26  ldc  [36]
         28  ldc  [42]
         30  invokestatic clojure.lang.RT.var(java.lang.String, java.lang.String) : clojure.lang.Var [30]
         33  checkcast clojure.lang.Var [32]
         36  putstatic no.disassemble$disassemble.const__2 : clojure.lang.Var [44]
         39  ldc  [36]
         41  ldc  [46]
         43  invokestatic clojure.lang.RT.var(java.lang.String, java.lang.String) : clojure.lang.Var [30]
         46  checkcast clojure.lang.Var [32]
         49  putstatic no.disassemble$disassemble.const__3 : clojure.lang.Var [48]
         52  ldc  [22]
         54  ldc  [50]
         56  invokestatic clojure.lang.RT.var(java.lang.String, java.lang.String) : clojure.lang.Var [30]
         59  checkcast clojure.lang.Var [32]
         62  putstatic no.disassemble$disassemble.const__4 : clojure.lang.Var [52]
         65  aconst_null
         66  ldc  [54]
         68  invokestatic clojure.lang.RT.keyword(java.lang.String, java.lang.String) : clojure.lang.Keyword [58]
         71  checkcast clojure.lang.Keyword [60]
         74  putstatic no.disassemble$disassemble.const__5 : clojure.lang.Keyword [62]
         77  ldc  [22]
         79  ldc  [64]
         81  invokestatic clojure.lang.RT.var(java.lang.String, java.lang.String) : clojure.lang.Var [30]
         84  checkcast clojure.lang.Var [32]
         87  putstatic no.disassemble$disassemble.const__6 : clojure.lang.Var [66]
         90  new clojure.lang.KeywordLookupSite [68]
         93  dup
         94  aconst_null
         95  ldc  [54]
         97  invokestatic clojure.lang.RT.keyword(java.lang.String, java.lang.String) : clojure.lang.Keyword [58]
        100  invokespecial clojure.lang.KeywordLookupSite(clojure.lang.Keyword) [72]
        103  dup
        104  putstatic no.disassemble$disassemble.__site__0__ : clojure.lang.KeywordLookupSite [74]
        107  putstatic no.disassemble$disassemble.__thunk__0__ : clojure.lang.ILookupThunk [76]
        110  return
          Line numbers:
            [pc: 0, line: 19]
      
      // Method descriptor #20 ()V
      // Stack: 1, Locals: 1
      public disassemble$disassemble();
        0  aload_0
        1  invokespecial clojure.lang.AFunction() [78]
        4  return
          Line numbers:
            [pc: 0, line: 19]
      
      // Method descriptor #80 (Ljava/lang/Object;)Ljava/lang/Object;
      // Stack: 9, Locals: 4
      public java.lang.Object invoke(java.lang.Object obj);
          0  getstatic no.disassemble$disassemble.const__0 : clojure.lang.Var [34]
          3  invokevirtual clojure.lang.Var.getRawRoot() : java.lang.Object [84]
          6  checkcast clojure.lang.IFn [86]
          9  getstatic no.disassemble$disassemble.const__1 : clojure.lang.Var [40]
         12  invokevirtual clojure.lang.Var.getRawRoot() : java.lang.Object [84]
         15  checkcast clojure.lang.IFn [86]
         18  aload_1 [obj]
         19  invokeinterface clojure.lang.IFn.invoke(java.lang.Object) : java.lang.Object [88] [nargs: 2]
         24  dup
         25  ifnull 40
         28  getstatic java.lang.Boolean.FALSE : java.lang.Boolean [94]
         31  if_acmpeq 41
         34  aload_1 [obj]
         35  aconst_null
         36  astore_1 [obj]
         37  goto 58
         40  pop
         41  getstatic no.disassemble$disassemble.const__2 : clojure.lang.Var [44]
         44  invokevirtual clojure.lang.Var.getRawRoot() : java.lang.Object [84]
         47  checkcast clojure.lang.IFn [86]
         50  aload_1 [obj]
         51  aconst_null
         52  astore_1 [obj]
         53  invokeinterface clojure.lang.IFn.invoke(java.lang.Object) : java.lang.Object [88] [nargs: 2]
         58  ldc  [96]
         60  invokestatic clojure.lang.Reflector.invokeNoArgInstanceMember(java.lang.Object, java.lang.String) : java.lang.Object [102]
         63  invokeinterface clojure.lang.IFn.invoke(java.lang.Object) : java.lang.Object [88] [nargs: 2]
         68  astore_2 [cls_name]
         69  getstatic no.disassemble$disassemble.const__4 : clojure.lang.Var [52]
         72  invokevirtual clojure.lang.Var.getRawRoot() : java.lang.Object [84]
         75  checkcast clojure.lang.IFn [86]
         78  invokeinterface clojure.lang.IFn.invoke() : java.lang.Object [104] [nargs: 1]
         83  aload_2 [cls_name]
         84  aconst_null
         85  astore_2 [cls_name]
         86  invokestatic clojure.lang.RT.get(java.lang.Object, java.lang.Object) : java.lang.Object [107]
         89  astore_3 [bytecode]
         90  new org.eclipse.jdt.internal.core.util.Disassembler [109]
         93  dup
         94  invokespecial org.eclipse.jdt.internal.core.util.Disassembler() [110]
         97  ldc  [112]
         99  iconst_3
        100  anewarray java.lang.Object [114]
        103  dup
        104  iconst_0
        105  aload_3 [bytecode]
        106  aconst_null
        107  astore_3 [bytecode]
        108  aastore
        109  dup
        110  iconst_1
        111  ldc  [116]
        113  aastore
        114  dup
        115  iconst_2
        116  getstatic no.disassemble$disassemble.__thunk__0__ : clojure.lang.ILookupThunk [76]
        119  dup
        120  getstatic no.disassemble$disassemble.const__6 : clojure.lang.Var [66]
        123  invokevirtual clojure.lang.Var.getRawRoot() : java.lang.Object [84]
        126  dup_x2
        127  invokeinterface clojure.lang.ILookupThunk.get(java.lang.Object) : java.lang.Object [120] [nargs: 2]
        132  dup_x2
        133  if_acmpeq 140
        136  pop
        137  goto 162
        140  swap
        141  pop
        142  dup
        143  getstatic no.disassemble$disassemble.__site__0__ : clojure.lang.KeywordLookupSite [74]
        146  swap
        147  invokeinterface clojure.lang.ILookupSite.fault(java.lang.Object) : clojure.lang.ILookupThunk [126] [nargs: 2]
        152  dup
        153  putstatic no.disassemble$disassemble.__thunk__0__ : clojure.lang.ILookupThunk [76]
        156  swap
        157  invokeinterface clojure.lang.ILookupThunk.get(java.lang.Object) : java.lang.Object [120] [nargs: 2]
        162  aastore
        163  invokestatic clojure.lang.Reflector.invokeInstanceMethod(java.lang.Object, java.lang.String, java.lang.Object[]) : java.lang.Object [130]
        166  areturn
          Line numbers:
            [pc: 0, line: 19]
            [pc: 0, line: 21]
            [pc: 9, line: 21]
            [pc: 9, line: 21]
            [pc: 9, line: 21]
            [pc: 41, line: 21]
            [pc: 69, line: 22]
            [pc: 69, line: 22]
            [pc: 90, line: 23]
            [pc: 116, line: 23]
          Local variable table:
            [pc: 69, pc: 166] local: cls_name index: 2 type: java.lang.Object
            [pc: 90, pc: 166] local: bytecode index: 3 type: java.lang.Object
            [pc: 0, pc: 166] local: this index: 0 type: java.lang.Object
            [pc: 0, pc: 166] local: obj index: 1 type: java.lang.Object
      
      // Method descriptor #137 (ILclojure/lang/ILookupThunk;)V
      // Stack: 1, Locals: 3
      public void swapThunk(int arg0, clojure.lang.ILookupThunk arg1);
         0  iload_1
         1  tableswitch default: 27
              case 0: 20
        20  aload_2
        21  putstatic no.disassemble$disassemble.__thunk__0__ : clojure.lang.ILookupThunk [76]
        24  goto 27
        27  return
    
    
    }
    nil
    no.disassemble=> 

License

Copyright © 2013 Gary Trakhman

Distributed under the Eclipse Public License, the same as Clojure.