Class: NodeMarshal

Inherits:
Object
  • Object
show all
Defined in:
lib/node-marshal.rb,
ext/node-marshal/nodedump.c

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#new(: srcfile, filename) ⇒ Object #new(: binfile, filename) ⇒ Object #new(: srcmemory, srcstr) ⇒ Object #new(: binmemory, binstr) ⇒ Object

Creates NodeMarshal class example from the source code or dumped syntax tree (NODEs), i.e. preparsed and packed source code. Created object can be used either for code execution or for saving it in the preparsed form (useful for code obfuscation/protection)



1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
# File 'ext/node-marshal/nodedump.c', line 1540

static VALUE m_nodedump_init(VALUE self, VALUE source, VALUE info)
{
  ID id_usr;
  Check_Type(source, T_SYMBOL);
  id_usr = SYM2ID(source);
  if (id_usr == rb_intern("srcfile"))
  {
    return m_nodedump_from_source(self, info);
  }
  else if (id_usr == rb_intern("srcmemory"))
  {
    return m_nodedump_from_string(self, info);
  }
  else if (id_usr == rb_intern("binmemory"))
  {
    return m_nodedump_from_memory(self, info);
  }
  else if (id_usr == rb_intern("binfile"))
  {
    VALUE cFile = rb_const_get(rb_cObject, rb_intern("File"));
    VALUE bin = rb_funcall(cFile, rb_intern("binread"), 1, info);
    return m_nodedump_from_memory(self, bin);
  }
  else
  {
    rb_raise(rb_eArgError, "Invalid source type (it must be :srcfile, :srcmemory, :binmemory of :binfile)");
  }
  return Qnil;
}

Class Method Details

.base85r_decode(input) ⇒ Object

Decode ASCII string in the modified BASE85 format to the binary string (useful for obfuscation of .rb source files)



1864
1865
1866
1867
# File 'ext/node-marshal/nodedump.c', line 1864

static VALUE m_base85r_decode(VALUE obj, VALUE input)
{
  return base85r_decode(input);
}

.base85r_encode(input) ⇒ Object

Encode arbitrary binary string to the ASCII string using modified version of BASE85 (useful for obfuscation of .rb source files)



1851
1852
1853
1854
# File 'ext/node-marshal/nodedump.c', line 1851

static VALUE m_base85r_encode(VALUE obj, VALUE input)
{
  return base85r_encode(input);
}

.compile_rb_file(outfile, inpfile, *args) ⇒ Object



83
84
85
86
87
# File 'lib/node-marshal.rb', line 83

def self.compile_rb_file(outfile, inpfile, *args)
  node = NodeMarshal.new(:srcfile, inpfile)
  node.to_compiled_rb(outfile, *args)
  return true
end

Instance Method Details

#change_literal(old_lit, new_lit) ⇒ Object

Update the array with the list of literals (to be used for code obfuscation) Warning! This function is a stub!



1437
1438
1439
1440
1441
# File 'ext/node-marshal/nodedump.c', line 1437

static VALUE m_nodedump_change_literal(VALUE self, VALUE old_lit, VALUE new_lit)
{
    /* TO BE IMPLEMENTED */
    return self;
}

#change_symbol(old_sym, new_sym) ⇒ Object

Replace one symbol by another (to be used for code obfuscation)



1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
# File 'ext/node-marshal/nodedump.c', line 1349

static VALUE m_nodedump_change_symbol(VALUE self, VALUE old_sym, VALUE new_sym)
{
  VALUE val_nodehash = rb_iv_get(self, "@nodehash");
  VALUE syms, key;
  // Check if node is position-independent
  // (i.e. with initialized NODEInfo structure that contains
  // relocations for symbols)
  if (val_nodehash == Qnil)
    rb_raise(rb_eArgError, "This node is not preparsed into Hash");
  // Check data types of the input array
  if (TYPE(old_sym) != T_STRING)
  {
    rb_raise(rb_eArgError, "old_sym argument must be a string");
  }
  if (TYPE(new_sym) != T_STRING)
  {
    rb_raise(rb_eArgError, "new_sym argument must be a string");
  }
  // Get the symbol table from the Hash
  syms = rb_hash_aref(val_nodehash, ID2SYM(rb_intern("symbols")));
  if (syms == Qnil)
    rb_raise(rb_eArgError, "Preparsed hash has no :symbols field");
  // Check if new_sym is present in the symbol table
  key = rb_funcall(syms, rb_intern("find_index"), 1, new_sym);
  if (key != Qnil)
  {
    rb_raise(rb_eArgError, "new_sym value must be absent in table of symbols");
  }
  // Change the symbol in the preparsed Hash
  key = rb_funcall(syms, rb_intern("find_index"), 1, old_sym);
  if (key == Qnil)
    return Qnil;
  RARRAY_PTR(syms)[FIX2INT(key)] = new_sym;
  return self;
}

#compileObject

Creates the RubyVM::InstructionSequence object from the node



1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
# File 'ext/node-marshal/nodedump.c', line 1450

static VALUE m_nodedump_compile(VALUE self)
{
  NODE *node = RNODE(rb_iv_get(self, "@node"));
  VALUE nodename = rb_iv_get(self, "@nodename");
  VALUE filename = rb_iv_get(self, "@filename");
  VALUE filepath = rb_iv_get(self, "@filepath");
#ifndef WITH_RB_ISEQW_NEW
  /* For Pre-2.3 */
  return rb_iseq_new_top(node, nodename, filename, filepath, Qfalse);
#else
  /* For Ruby 2.3 */
  return rb_iseqw_new(rb_iseq_new_top(node, nodename, filename, filepath, Qfalse));
#endif
}

#dump_treeObject

Transforms Ruby syntax tree (NODE) to the text string using rb_parser_dump_tree function from node.c (see Ruby source code).



1577
1578
1579
1580
1581
# File 'ext/node-marshal/nodedump.c', line 1577

static VALUE m_nodedump_parser_dump_tree(VALUE self)
{
  NODE *node = RNODE(rb_iv_get(self, "@node"));
  return rb_parser_dump_tree(node, 0);
}

#dump_tree_shortObject

Prints the node tree in the short variant



1586
1587
1588
1589
1590
1591
1592
# File 'ext/node-marshal/nodedump.c', line 1586

static VALUE m_nodedump_dump_tree_short(VALUE self)
{
  VALUE str = rb_str_new2(""); // Output string
  NODE *node = RNODE(rb_iv_get(self, "@node"));
  print_node(str, node, 0);
  return str;
}

#filenameObject

Returns name of file that was used for node generation and will be used by YARV (or nil/<compiled> if a string of code was used)



1790
1791
1792
1793
# File 'ext/node-marshal/nodedump.c', line 1790

static VALUE m_nodedump_filename(VALUE self)
{
  return rb_funcall(rb_iv_get(self, "@filename"), rb_intern("dup"), 0);
}

#filename=(val) ⇒ Object

Sets name of file that was used for node generation and will be used by YARV (or nil/<compiled> if a string of code was used)



1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
# File 'ext/node-marshal/nodedump.c', line 1799

static VALUE m_nodedump_set_filename(VALUE self, VALUE val)
{
  if (val != Qnil)
  {
    Check_Type(val, T_STRING);
    rb_iv_set(self, "@filename", rb_funcall(val, rb_intern("dup"), 0)); 
  }
  else
  {
    rb_iv_set(self, "@filename", Qnil);
  }
  return self;
}

#filepathObject

Returns path of file that was used for node generation and will be used by YARV (or nil/<compiled> if a string of code was used)



1817
1818
1819
1820
# File 'ext/node-marshal/nodedump.c', line 1817

static VALUE m_nodedump_filepath(VALUE self)
{
  return rb_funcall(rb_iv_get(self, "@filepath"), rb_intern("dup"), 0);
}

#filepath=Object

Sets the path of file that was used for node generation and will be used by YARV (or nil/<compiled> if a string of code was used)



1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
# File 'ext/node-marshal/nodedump.c', line 1829

static VALUE m_nodedump_set_filepath(VALUE self, VALUE val)
{
  if (val != Qnil)
  {
    Check_Type(val, T_STRING);
    rb_iv_set(self, "@filepath", rb_funcall(val, rb_intern("dup"), 0));
  }
  else
  {
    rb_iv_set(self, "@filepath", Qnil);
  }
  return self;
}

#get_aliases_table(our_symbols) ⇒ Object

call-seq:

obj.get_aliases_table(our_symbols)

Returns a hash that has “old_sym_name”=>“new_sym_name”,… format. “new_sym_name” are generated automatically.

  • our_symbols – An array that contains the list of symbols (AS STRINGS,

NOT AS SYMBOLS) that can be renamed.



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/node-marshal.rb', line 147

def get_aliases_table(our_symbols)
  symbols_ary = get_safe_symbols(our_symbols)
  pos = 0;
  aliases_ary = symbols_ary.map do |sym|
    pos += 1
    if sym.length > 1 && sym[0..1] == '@@'
      "@@q#{pos}"
    elsif sym[0] == '@'
      "@q#{pos}"
    elsif sym[0] =~ /[A-Z]/
      "Q#{pos}"
    elsif sym[0] =~ /[a-z]/
      "q#{pos}"
    end
  end
  [symbols_ary, aliases_ary].transpose.to_h    
end

#get_safe_symbols(our_symbols) ⇒ Object

call-seq:

obj.get_safe_symbols

Returns an array that contains strings with the names of symbols that are safe to change. It excludes symbols that are present in the table of literals (and their derivatives such as @x and x=). Such operation is useful for attr_readed, attr_writer and another similar metaprogramming techniques handling

  • our_symbols symbols created during node creation (must be found manually by the user by means of Symbol.all_symbols calling BEFORE and AFTER node creation.



129
130
131
132
133
134
135
136
137
# File 'lib/node-marshal.rb', line 129

def get_safe_symbols(our_symbols)
  self.to_hash # To initialize Hash with preparsed Ruby AST NODE
  symbolic_literals =  self.literals.select {|x| x.is_a?(Symbol)}.map {|x| x.to_s}
  fixed_symbols = [] + symbolic_literals
  fixed_symbols += symbolic_literals.map {|x| "@#{x}"}
  fixed_symbols += symbolic_literals.map {|x| "#{x}="}
  our_symbols = our_symbols.dup
  our_symbols -= fixed_symbols
end

#inspectObject

Gives the information about the node



1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
# File 'ext/node-marshal/nodedump.c', line 1684

static VALUE m_nodedump_inspect(VALUE self)
{
  static char str[1024], buf[512];
  VALUE num_of_nodes, nodename, filepath, filename;
  VALUE val_obj_addresses, val_nodeinfo;
  // Get generic information about node
  num_of_nodes = rb_iv_get(self, "@num_of_nodes");
  nodename = rb_iv_get(self, "@nodename");
  filepath = rb_iv_get(self, "@filepath");
  filename = rb_iv_get(self, "@filename");
  // Generate string with generic information about node
  sprintf(str,
    "----- NodeMarshal:0x%"PRIxPTR"\n"
    "    num_of_nodes: %d\n    nodename: %s\n    filepath: %s\n    filename: %s\n",
    (uintptr_t) (self),
    (num_of_nodes == Qnil) ? -1 : FIX2INT(num_of_nodes),
    (nodename == Qnil) ? "nil" : RSTRING_PTR(nodename),
    (filepath == Qnil) ? "nil" : RSTRING_PTR(filepath),
    (filename == Qnil) ? "nil" : RSTRING_PTR(filename)
    );
  // Check if the information about node struct is available
  val_nodeinfo = rb_iv_get(self, "@nodeinfo");
  val_obj_addresses = rb_iv_get(self, "@obj_addresses");
  if (val_nodeinfo == Qnil && val_obj_addresses == Qnil)
  {
    m_nodedump_to_hash(self);
    val_nodeinfo = rb_iv_get(self, "@nodeinfo");
  }
  // Information about preparsed node
  // a) NODEInfo struct
  if (val_nodeinfo == Qnil)
  {
    sprintf(buf, "    NODEInfo struct is empty\n");
  }
  else
  {
    NODEInfo *ninfo;
    Data_Get_Struct(val_nodeinfo, NODEInfo, ninfo);
    sprintf(buf, 
      "    NODEInfo struct:\n"
      "      syms hash len (Symbols):         %d\n"
      "      lits hash len (Literals):        %d\n"
      "      idtabs hash len (ID tables):     %d\n"
      "      gentries hash len (Global vars): %d\n"
      "      nodes hash len (Nodes):          %d\n"
#ifdef USE_RB_ARGS_INFO
      "      args hash len (args info):       %d\n"
#endif
      ,
      FIX2INT(rb_funcall(ninfo->syms.vals, rb_intern("length"), 0)),
      FIX2INT(rb_funcall(ninfo->lits.vals, rb_intern("length"), 0)),
      FIX2INT(rb_funcall(ninfo->idtabs.vals, rb_intern("length"), 0)),
      FIX2INT(rb_funcall(ninfo->gentries.vals, rb_intern("length"), 0)),
      FIX2INT(rb_funcall(ninfo->nodes.vals, rb_intern("length"), 0))
#ifdef USE_RB_ARGS_INFO
      ,
      FIX2INT(rb_funcall(ninfo->args.vals, rb_intern("length"), 0))
#endif
    );
  }
  strcat(str, buf);
  // b) NODEObjAddresses struct
  if (val_obj_addresses == Qnil)
  {
    sprintf(buf, "    NODEObjAddresses struct is empty\n");
  }
  else
  {
    NODEObjAddresses *objadr;
    Data_Get_Struct(val_obj_addresses, NODEObjAddresses, objadr);
    sprintf(buf, 
      "    NODEObjAddresses struct:\n"
      "      syms_len (Num of symbols):      %d\n"
      "      lits_len (Num of literals):     %d\n"
      "      idtbls_len (Num of ID tables):  %d\n"
      "      gvars_len (Num of global vars): %d\n"
      "      nodes_len (Num of nodes):       %d\n"
#ifdef USE_RB_ARGS_INFO
      "      args_len: (Num of args info):   %d\n"
#endif
      , objadr->syms_len, objadr->lits_len,
      objadr->idtbls_len, objadr->gvars_len,
      objadr->nodes_len
#ifdef USE_RB_ARGS_INFO
      , objadr->args_len
#endif
    );
  }
  strcat(str, buf);
  strcat(str, "------------------\n");
  // Generate output string
  return rb_str_new2(str);
}

#literalsObject

Return array with the list of literals



1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
# File 'ext/node-marshal/nodedump.c', line 1388

static VALUE m_nodedump_literals(VALUE self)
{
  int i;
  VALUE val_relocs, val_nodeinfo, lits;
  // Variant 1: node loaded from file. It uses NODEObjAddresses struct
  // with the results of Ruby NODE structure parsing.
  val_relocs = rb_iv_get(self, "@obj_addresses");
  if (val_relocs != Qnil)
  {
    NODEObjAddresses *relocs;

    Data_Get_Struct(val_relocs, NODEObjAddresses, relocs);
    lits = rb_ary_new();
    for (i = 0; i < relocs->lits_len; i++)
    {
      VALUE val = relocs->lits_adr[i];
      int t = TYPE(val);
      if (t != T_SYMBOL && t != T_FLOAT && t != T_FIXNUM)
        val = rb_funcall(val, rb_intern("dup"), 0);
      rb_ary_push(lits, val);
    }
    return lits;
  }
  // Variant 2: node saved to file (parsed from memory). It uses
  // NODEInfo struct that is initialized during node dump parsing.
  val_nodeinfo = rb_iv_get(self, "@nodeinfo");
  if (val_nodeinfo != Qnil)
  {
    NODEInfo *ninfo;
    VALUE *ary;
    Data_Get_Struct(val_nodeinfo, NODEInfo, ninfo);
    lits = rb_funcall(ninfo->lits.vals, rb_intern("values"), 0);
    ary = RARRAY_PTR(lits);
    for (i = 0; i < RARRAY_LEN(lits); i++)
    {
      int t = TYPE(ary[i]);
      if (t != T_SYMBOL && t != T_FLOAT && t != T_FIXNUM)
        ary[i] = rb_funcall(ary[i], rb_intern("dup"), 0);
    }
    return lits;
  }
  rb_raise(rb_eArgError, "Literals information not initialized. Run to_hash before reading.");
}

#nodeObject

Returns node object



1886
1887
1888
1889
# File 'ext/node-marshal/nodedump.c', line 1886

static VALUE m_nodedump_node(VALUE self)
{
  return rb_iv_get(self, "@node");
}

#nodenameObject

Returns node name (usually <main>)



1781
1782
1783
1784
# File 'ext/node-marshal/nodedump.c', line 1781

static VALUE m_nodedump_nodename(VALUE self)
{
  return rb_funcall(rb_iv_get(self, "@nodename"), rb_intern("dup"), 0);
}

#rebuildObject

call-seq:

obj.rebuld

Rebuilds the node by converting it to the binary dump and further restoring of it from this dump. It doesn’t change the original node and returns rebuilt node.



197
198
199
# File 'lib/node-marshal.rb', line 197

def rebuild
  NodeMarshal.new(:binmemory, to_bin)
end

#rename_ivars(*args) ⇒ Object

call-seq:

obj.rename_ivars


167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/node-marshal.rb', line 167

def rename_ivars(*args)
  if args.size == 0
    excl_names = []
  else
    excl_names = args[0]
  end

  to_hash
  syms = @nodehash[:symbols].select {|x| (x =~ /@[^@]/) == 0}
  pos = 1;
  syms_new = syms.map do |x|
    if excl_names.find_index(x[1..-1]) != nil
      str = x
    else
      str = "@ivar#{pos}"
    end
    pos = pos + 1;
    str
  end
  syms_subs =  [syms, syms_new].transpose.to_h
  replace_symbols(syms_subs)
  self
end

#replace_symbols(syms_subs) ⇒ Object

call-seq:

obj.replace_symbols(syms_subs)

Replaces some symbols inside parsed AST to user-defined aliases. It is designed to make code obfuscation easier. Be careful when using this ability: it is possible to break external libraries calls, operators overloading and some metaprogramming techniques.

  • syms_subs – Hash with the table of aliases. Keys are original names,

values are aliases. Keys and values MUST BE strings (not symbols!).



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/node-marshal.rb', line 98

def replace_symbols(syms_subs)
  # Check input data
  # a) type
  if !(syms_subs.is_a?(Hash))
    raise "symb_subs must be a hash"
  end
  # b) uniqueness of values inside the hash
  values = syms_subs.values
  if values.size != values.uniq.size
    raise ArgumentError, "values (new names) must be unique"
  end
  # c) uniqueness of values after replacement
  # TODO: MAKE IT!!!
  # Use NodeMarshal C part to replace the symbols
  self.to_hash # To initialize Hash with preparsed Ruby AST NODE
  syms_subs.each do |key, value|
    change_symbol(key, value)
  end
  self
end

#symbolsObject

Return array with the list of symbols



1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
# File 'ext/node-marshal/nodedump.c', line 1312

static VALUE m_nodedump_symbols(VALUE self)
{
  int i;
  VALUE val_relocs, val_nodeinfo, syms;
  // Variant 1: node loaded from file
  val_relocs = rb_iv_get(self, "@obj_addresses");
  if (val_relocs != Qnil)
  {
    NODEObjAddresses *relocs;
    Data_Get_Struct(val_relocs, NODEObjAddresses, relocs);
    syms = rb_ary_new();
    for (i = 0; i < relocs->syms_len; i++)
      rb_ary_push(syms, ID2SYM(relocs->syms_adr[i]));
    return syms;
  }
  // Variant 2: node saved to file (parsed from memory)
  val_nodeinfo = rb_iv_get(self, "@nodeinfo");
  if (val_nodeinfo != Qnil)
  {
    NODEInfo *ninfo;
    VALUE *ary;
    Data_Get_Struct(val_nodeinfo, NODEInfo, ninfo);
    syms = rb_funcall(ninfo->syms.vals, rb_intern("values"), 0);
    ary = RARRAY_PTR(syms);
    for (i = 0; i < RARRAY_LEN(syms); i++)
    {
      ary[i] = rb_funcall(ary[i], rb_intern("to_sym"), 0);
    }
    return syms;
  }
  rb_raise(rb_eArgError, "Symbol information not initialized. Run to_hash before reading.");
}

#to_binObject

Converts NodeMarshal class example to the binary string that can be saved to the file and used for loading the node from the file. Format of the obtained binary dump depends on used platform (especially size of the pointer) and Ruby version.



1674
1675
1676
1677
1678
1679
# File 'ext/node-marshal/nodedump.c', line 1674

static VALUE m_nodedump_to_bin(VALUE self)
{
  VALUE hash = m_nodedump_to_hash(self);
  VALUE cMarshal = rb_const_get(rb_cObject, rb_intern("Marshal"));
  return rb_funcall(cMarshal, rb_intern("dump"), 1, hash);
}

#to_compiled_rb(outfile, *args) ⇒ Object

call-seq:

obj.to_compiled_rb(outfile, opts)

Transforms node to the Ruby file

  • outfile – name of the output file

  • opts – Hash with options (:compress, :so_path) :compress can be true or false, :so_path is a test string with the command for nodemarshal.so inclusion (default is require_relative ‘../ext/node-marshal/nodemarshal.so’)



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/node-marshal.rb', line 35

def to_compiled_rb(outfile, *args)
  compress = true
  so_path = "require_relative '../ext/node-marshal/nodemarshal.so'"
  if args.length > 0
    opts = args[0]
    if opts.has_key?(:compress)
      compress = opts[:compress]
    end
    if opts.has_key?(:so_path)
      so_path = opts[:so_path]
    end
  end
  # Compression
  if compress
    if !defined?(Zlib)
      raise "Compression is not supported: Zlib is absent"
    end
    zlib_include = "require 'zlib'"
    data_txt = NodeMarshal.base85r_encode(Zlib::deflate(self.to_bin))
    data_bin = "Zlib::inflate(NodeMarshal.base85r_decode(data_txt))"
  else
    zlib_include = "# No compression"
    data_txt = self.to_text
    data_bin = "NodeMarshal.base85r_decode(data_txt)"
  end
  # Document header
  txt = "# Ruby compressed source code\n# RUBY_PLATFORM: \#{RUBY_PLATFORM}\n# RUBY_VERSION: \#{RUBY_VERSION}\n\#{zlib_include}\n\#{so_path}\ndata_txt = <<DATABLOCK\n\#{data_txt}\nDATABLOCK\ndata_bin = \#{data_bin}\nnode = NodeMarshal.new(:binmemory, data_bin)\nnode.filename = __FILE__\nnode.filepath = File.expand_path(node.filename)\nnode.compile.eval\n"
  # Process input arguments
  if outfile != nil
    File.open(outfile, 'w') {|fp| fp << txt}
  end
  return txt
end

#to_hashObject

Converts NodeMarshal class example to the hash that contains full and independent from data structures memory addresses information. Format of the obtained hash depends on used platform (especially size of the pointer) and Ruby version.

Format of the hash

Part 1: Signatures

  • MAGIC – NODEMARSHAL10

  • RUBY_PLATFORM – saved RUBY_PLATFORM constant value

  • RUBY_VERSION – saved RUBY_VERSION constant value

Part 2: Program loadable elements.

All loadable elements are arrays. Index of the array element means its identifier that is used in the node tree.

  • literals – program literals (strings, ranges etc.)

  • symbols – program symbols

  • global_entries – global variables information

  • id_tables – array of arrays. Each array contains symbols IDs

  • args – information about code block argument(s)

Part 3: Nodes information

  • nodes – string that contains binary encoded information about the nodes

  • num_of_nodes – number of nodes in the nodes field

  • nodename – name of the node (usually “<main>”)

  • filename – name (without path) of .rb file used for the node generation

  • filepath – name (with full path) of .rb file used for the node generation



1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
# File 'ext/node-marshal/nodedump.c', line 1630

static VALUE m_nodedump_to_hash(VALUE self)
{
  NODE *node = RNODE(rb_iv_get(self, "@node"));
  NODEInfo *info;
  VALUE ans, num, val_info;
  // DISABLE GARBAGE COLLECTOR (important for dumping)
  rb_gc_disable();
  // Convert the node to the form with relocs (i.e. the information about node)
  // if such form is not present
  val_info = rb_iv_get(self, "@nodeinfo");
  if (val_info == Qnil)
  {
    val_info = Data_Make_Struct(cNodeInfo, NODEInfo,
      NODEInfo_mark, NODEInfo_free, info); // This data envelope cannot exist without NODE
    NODEInfo_init(info);
    rb_iv_set(self, "@nodeinfo", val_info);
    num = INT2FIX(count_num_of_nodes(node, node, info));
    rb_iv_set(self, "@nodeinfo_num_of_nodes", num);
    // Convert node to NODEInfo structure
    ans = NODEInfo_toHash(info);
    rb_hash_aset(ans, ID2SYM(rb_intern("num_of_nodes")), num);
    rb_hash_aset(ans, ID2SYM(rb_intern("nodename")), rb_iv_get(self, "@nodename"));
    rb_hash_aset(ans, ID2SYM(rb_intern("filename")), rb_iv_get(self, "@filename"));
    rb_hash_aset(ans, ID2SYM(rb_intern("filepath")), rb_iv_get(self, "@filepath"));
    rb_iv_set(self, "@nodehash", ans);
  }
  else
  {
    ans = rb_iv_get(self, "@nodehash");
  }
  // ENABLE GARBAGE COLLECTOR (important for dumping)
  rb_gc_enable();
  return ans;
}

#to_binObject

Converts NodeMarshal class example to the text string (modified Base85 encoding) that can be saved to the file and used for loading the node from the file. Format of the obtained binary dump depends on used platform (especially size of the pointer) and Ruby version.



1877
1878
1879
1880
1881
# File 'ext/node-marshal/nodedump.c', line 1877

static VALUE m_nodedump_to_text(VALUE self)
{
  VALUE bin = m_nodedump_to_bin(self);
  return base85r_encode(bin);
}