Permalink
Newer
Older
100644 855 lines (742 sloc) 27.4 KB
2
using System.IO;
3
using System.Security.Cryptography;
4
using System.Collections.Generic;
5
using System.Text;
6
using Niecza.CLRBackend;
8
// Here in Niecza we have four different kinds of unit scopes:
9
//
10
// * COMPILING::UNIT, aka RuntimeUnit: one of these exists for every
11
// call into the compiler, whether eval or module. The REPL will
12
// be considered as if it were eval.
13
//
14
// * Serialization scopes, which are created when compiling modules
15
// or when pre-compiling a main program. During execution there is
16
// no current serialization scope; evals inherit the serialization
17
// scope or lack thereof that was in effect.
18
//
19
// * Assembly scopes, which are an artifact of the CLR and almost align
20
// with precompilation scopes, except that they have to exist always
21
// because methods cannot be created free-floating in CLR 2.x.
22
//
23
// An assembly scope is created for all serialization scopes, and
24
// non-saved anonymous assemblies are created for eval-and-run of
25
// a file and non-BEGIN-time evals.
26
//
27
// * GLOBAL scope is very much like serialization scope except that there
28
// is a "true globals" scope that is used when not serializing.
29
30
// This implements "bounded serialization" for Niecza. Unfortunately
31
// the CLR's builtin serialization can't efficiently be made bounded,
32
// and anyway it would be nice if the serialization format could be
33
// transported across backends.
34
35
// TODO: implement a more Storable-like interface.
36
37
// Note, the serialization subsystem is *NOT* thread safe !
38
namespace Niecza.Serialization {
39
// Information kept on a serialization unit after loading or storing,
40
// but not before storing.
41
class SerUnit {
42
internal string name; // eg "File.Copy"
43
internal byte[] hash; // hash of entire file, filled at write time
44
internal object[] bynum = new object[8]; // objects in unit
45
internal object root; // the RuntimeUnit object
46
internal int nobj; // = 0
48
49
// The central feature of *bounded* serialization is that object
50
// registries are kept distinct from the (de)serializer, and can
51
// be shared between serialization runs.
52
class ObjectRegistry {
53
// TODO: investigate a more specialized representation,
54
// ideally not having to hash as many objects
55
struct ObjRef {
56
public SerUnit unit;
57
public int id;
59
Dictionary<object,ObjRef> byref = new Dictionary<object,ObjRef>();
61
Dictionary<string,SerUnit> units =
62
new Dictionary<string,SerUnit>();
63
64
internal static HashAlgorithm NewHash() { return new SHA256Managed(); }
65
66
static readonly string signature = "Niecza-Serialized-Module";
67
static readonly int version = 20;
68
69
// Routines for use by serialization code
70
public bool CheckWriteObject(SerUnit into, object o,
71
out SerUnit lui, out int id) {
72
ObjRef or;
73
if (byref.TryGetValue(o, out or)) {
74
lui = or.unit;
75
id = or.id;
79
if (into.nobj == into.bynum.Length)
80
Array.Resize(ref into.bynum, into.nobj * 2);
82
or.unit = lui = into;
83
id = or.id = into.nobj++;
84
into.bynum[id] = o;
85
86
byref[o] = or;
87
88
return false;
89
}
91
// Undo previous method...
92
public void DeleteLastObject(SerUnit into) {
93
byref.Remove(into.bynum[--into.nobj]);
94
into.bynum[into.nobj] = null;
95
}
96
97
public void RegisterThawed(SerUnit into, object o) {
98
ObjRef or;
99
if (into.nobj == into.bynum.Length)
100
Array.Resize(ref into.bynum, into.nobj * 2);
101
102
or.unit = into;
103
or.id = into.nobj++;
104
into.bynum[or.id] = o;
105
106
byref[o] = or;
107
}
108
109
// Routines for use by compilation manager
110
111
// Loads a single unit from the compiled-data directory.
112
// Will throw a ThawException if a stale reference is encountered
113
// or other data format error.
114
public SerUnit LoadUnit(string name) {
115
SerUnit su;
116
117
// is the unit already loaded?
118
if (units.TryGetValue(name, out su))
119
return su;
120
121
string file = Path.Combine(Backend.obj_dir, Backend.prefix +
122
name.Replace("::",".") + ".ser");
123
byte[] bytes = File.ReadAllBytes(file);
124
125
su = new SerUnit();
126
su.name = name;
127
su.hash = NewHash().ComputeHash(bytes);
129
ThawBuffer tb = new ThawBuffer(this, su, bytes);
131
units[name] = su;
132
bool success = false;
133
try {
134
string rsig = tb.String();
135
if (rsig != signature)
136
throw new ThawException("signature mismatch loading " + file);
137
int rver = tb.Int();
138
if (rver != version)
139
throw new ThawException("version mismatch loading " + file);
140
141
su.root = tb.ObjRef();
143
success = true;
144
} finally {
145
// don't leave half-read units in the map
146
if (!success)
147
UnloadUnit(name);
148
}
149
150
return su;
151
}
152
153
// removes a stale unit so a new version can be saved over it.
154
public void UnloadUnit(string name) {
155
if (!units.ContainsKey(name))
156
return;
157
SerUnit su = units[name];
158
units.Remove(name);
159
160
for (int i = 0; i < su.nobj; i++)
161
byref.Remove(su.bynum[i]);
162
}
163
164
public SerUnit SaveUnit(string name, IFreeze root) {
165
SerUnit su = new SerUnit();
166
su.name = name;
167
su.root = root;
168
169
if (units.ContainsKey(name))
170
throw new InvalidOperationException("unit " +name+ " exists");
171
172
bool success = false;
173
string file = Path.Combine(Backend.obj_dir, Backend.prefix +
174
name.Replace("::",".") + ".ser");
175
176
FreezeBuffer fb = new FreezeBuffer(this, su);
177
178
units[name] = su;
179
180
try {
181
fb.String(signature);
182
fb.Int(version);
183
fb.ObjRef(root);
184
185
byte[] data = fb.GetData();
186
su.hash = NewHash().ComputeHash(data);
187
File.WriteAllBytes(file, data);
188
success = true;
189
} finally {
190
if (!success)
191
UnloadUnit(name);
192
}
193
194
return su;
195
}
196
}
197
198
// One of these codes is written at the beginning of every object ref
199
enum SerializationCode : byte {
200
// special
201
Null,
202
203
// existing objects
204
ForeignRef,
205
SelfRef,
206
NewUnitRef,
207
208
// types of new object
209
RuntimeUnit,
210
SubInfo,
211
STable,
212
StashEnt,
213
Rat,
214
FatRat,
215
Complex,
216
BigInteger,
217
VarDeque,
218
VarHash,
219
DispatchEnt,
220
RxFrame,
221
P6how,
223
CC,
224
AltInfo,
225
Signature,
226
Parameter,
227
228
// types of P6any-reified object
229
P6opaque, // eventually let's specialize this
230
Frame,
231
Cursor,
232
233
// miscellany - keep these in same order as FallbackFreeze
234
String,
235
ArrP6any,
236
ArrVariable,
237
ArrString,
238
ArrCC,
239
Boolean,
245
SimpleVariable, // allow 4 for flags
246
SimpleVariable_1,
247
SimpleVariable_2,
248
SimpleVariable_3,
249
SubstrLValue,
251
252
// vivification hooks
254
ArrayViviHook,
255
NewArrayViviHook,
256
HashViviHook,
257
NewHashViviHook,
258
259
// Longest-token automaton descriptors
260
LADNone, // no-args
261
LADNull,
262
LADDot,
263
LADDispatcher,
264
LADImp,
265
LADStr, // string
266
LADStrNoCase,
267
LADMethod,
268
LADParam,
270
LADSequence, // LAD[]
271
LADAny,
272
LADCC, // CC
273
}
274
275
// An instance of this class is used to serialize serialization units
276
public class FreezeBuffer {
277
byte[] data;
278
int wpointer;
279
280
Dictionary<SerUnit,int> unit_to_offset;
281
int usedunits;
282
283
ObjectRegistry reg;
284
SerUnit unit;
286
internal FreezeBuffer(ObjectRegistry reg, SerUnit unit) {
287
if (reg == null || unit == null)
288
throw new ArgumentNullException();
290
this.unit = unit;
291
unit_to_offset = new Dictionary<SerUnit,int>();
292
data = new byte[256];
293
}
294
295
internal byte[] GetData() {
296
byte[] ret = new byte[wpointer];
297
Array.Copy(data, ret, ret.Length);
298
return ret;
299
}
300
301
void Ensure(int ct) {
302
while (ct + wpointer > data.Length)
303
Array.Resize(ref data, data.Length * 2);
304
}
305
306
public void Byte(byte x) {
307
Ensure(1);
308
data[wpointer++] = x;
309
}
310
311
public void Long(long x) {
312
//Console.WriteLine("Saving {0} at {1}", x, wpointer);
313
Ensure(10);
314
while (true) {
315
if (x >= -64 && x <= 63) {
316
data[wpointer++] = (byte) (127 & (byte)x);
317
break;
318
} else {
319
data[wpointer++] = (byte) (128 | (byte)x);
320
x >>= 7;
321
}
322
}
325
public void ULong(ulong x) {
326
//Console.WriteLine("Saving {0} at {1}", x, wpointer);
327
Ensure(10);
328
while (true) {
329
if (x <= 127) {
330
data[wpointer++] = (byte) (127 & (byte)x);
331
break;
332
} else {
333
data[wpointer++] = (byte) (128 | (byte)x);
334
x >>= 7;
335
}
336
}
339
public void Short(short x) { Long(x); }
340
public void Int(int x) { Long(x); }
342
public void Double(double x) {
343
Long(BitConverter.DoubleToInt64Bits(x));
344
}
345
346
public void String(string s) {
347
if (s == null) {
348
Int(-1);
349
} else {
350
Int(s.Length);
351
foreach (char ch in s)
352
ULong((ulong)ch);
356
public void Strings(string[] s) {
357
if (s == null) Int(-1);
358
else {
359
Int(s.Length);
360
foreach (string ch in s) String(ch);
361
}
362
}
363
364
public void Ints(int[] s) {
365
if (s == null) {
366
Int(-1);
367
} else {
368
Int(s.Length);
369
foreach (int ch in s)
370
Int(ch);
371
}
372
}
373
375
if (x == null) {
376
Int(-1);
377
} else {
378
Int(x.Length);
379
foreach (T y in x)
380
ObjRef(y);
381
}
382
}
383
384
public void Refs<T> (IList<T> x) {
385
if (x == null) {
386
Int(-1);
387
} else {
388
Int(x.Count);
389
foreach (T y in x)
390
ObjRef(y);
391
}
392
}
393
394
public void Ephemeralize() {
395
Byte((byte)SerializationCode.Null);
396
reg.DeleteLastObject(unit);
397
}
398
399
// This is the main routine you should call from your Freeze
400
// callbacks to freeze an object
402
int id;
403
SerUnit altunit;
404
if (Config.SerTrace)
405
Console.WriteLine("Saving {0} at {1}...", o, wpointer);
406
if (o == null) { // null pointers are special
407
Byte((byte)SerializationCode.Null);
408
return;
409
}
410
411
if (reg.CheckWriteObject(unit, o, out altunit, out id)) {
412
if (altunit == unit) {
413
Byte((byte)SerializationCode.SelfRef);
414
} else {
415
int altcode;
416
if (!unit_to_offset.TryGetValue(altunit, out altcode)) {
417
Byte((byte)SerializationCode.NewUnitRef);
418
String(altunit.name);
419
// save the hash too so stale refs can be caught
420
foreach (byte b in altunit.hash) Byte(b);
421
422
unit_to_offset[altunit] = usedunits++;
423
} else {
424
Byte((byte)SerializationCode.ForeignRef);
425
Int(altcode);
426
}
427
}
428
Int((int)id);
429
} else {
430
// must take responsibility for saving the tag
431
IFreeze f = o as IFreeze;
432
if (f != null) {
433
f.Freeze(this);
434
} else {
435
FallbackFreeze(o);
436
}
437
}
438
}
439
440
[Immutable]
441
internal static Type[] boxTypes = new Type[] {
442
null, typeof(Rat), typeof(FatRat), typeof(Complex),
443
typeof(double), typeof(int), typeof(string), typeof(VarHash),
444
typeof(Variable[]), typeof(VarDeque), typeof(STable),
445
typeof(BigInteger),
447
[Immutable]
448
internal static Func<P6opaque>[] boxCreate = new Func<P6opaque>[] {
449
P6opaque.Create, BoxObject<Rat>.Create, BoxObject<FatRat>.Create,
450
BoxObject<Complex>.Create, BoxObject<double>.Create,
451
BoxObject<int>.Create, BoxObject<string>.Create,
452
BoxObject<VarHash>.Create, BoxObject<Variable[]>.Create,
453
BoxObject<VarDeque>.Create, BoxObject<STable>.Create,
454
BoxObject<BigInteger>.Create,
456
[Immutable]
457
static Type[] anyTypes = new Type[] {
458
typeof(string), typeof(P6any[]), typeof(Variable[]),
459
typeof(string[]), typeof(CC[]),
460
typeof(bool), typeof(int), typeof(double), typeof(Type),
461
};
462
463
void FallbackFreeze(object o) {
464
int ix = 0;
465
Type t = o.GetType();
466
while (ix != anyTypes.Length && anyTypes[ix] != t) ix++;
467
Byte((byte)(((int)SerializationCode.String) + ix));
468
469
switch(ix) {
470
case 0:
471
String((string)o);
472
break;
473
case 1:
474
Refs((P6any[])o);
475
break;
476
case 2:
477
Refs((Variable[])o);
478
break;
479
case 3:
480
Refs((string[])o);
483
Refs((CC[])o);
484
break;
485
case 5:
486
Byte((byte)((bool)o ? 1 : 0));
489
Int((int)o);
490
break;
491
case 7:
492
Double((double)o);
493
break;
494
case 8:
495
String(((Type)o).AssemblyQualifiedName);
496
break;
497
default:
498
throw new NotImplementedException(t.FullName);
499
}
500
}
501
}
502
503
// Note that this interface only handles freezing - thaw is done using
504
// a switch statement.
505
public interface IFreeze {
506
void Freeze(FreezeBuffer fb);
507
}
508
// implement this if you need to copy in data from other objects, &c
509
interface IFixup {
510
void Fixup();
511
}
512
513
class ThawBuffer {
514
byte[] data;
515
int rpointer;
516
ObjectRegistry reg;
517
518
SerUnit[] unit_map = new SerUnit[8];
519
int refed_units;
520
SerUnit unit;
522
List<IFixup> fixups_needed = new List<IFixup>();
523
List<object> revalidate = new List<object>();
525
public Type type;
526
public Dictionary<string,System.Reflection.MethodInfo> methods;
528
internal ThawBuffer(ObjectRegistry reg, SerUnit unit, byte[] data) {
529
this.data = data;
530
this.reg = reg;
531
this.unit = unit;
534
internal void RunFixups() {
535
P6how.BulkRevalidate(revalidate);
536
foreach (IFixup f in fixups_needed)
537
f.Fixup();
538
fixups_needed.Clear();
539
}
540
541
internal void PushFixup(IFixup f) {
542
fixups_needed.Add(f);
543
}
544
545
internal void PushRevalidate(STable f) {
546
revalidate.Add(f);
547
}
548
549
public byte Byte() { return data[rpointer++]; }
550
551
public long Long() {
552
int shift = 0;
553
long accum = 0;
554
while (true) {
555
byte b = Byte();
556
accum |= (((long)(b & 127)) << shift);
557
shift += 7;
558
if ((b & 128) == 0) {
559
if ((b & 64) != 0 && shift < 64) {
560
accum |= ((-1L) << shift);
561
}
562
//Console.WriteLine("Read {0} end {1}", accum, rpointer);
563
return accum;
564
}
565
}
568
public ulong ULong() {
569
int shift = 0;
570
ulong accum = 0;
571
while (true) {
572
byte b = Byte();
573
accum |= (((ulong)(b & 127)) << shift);
574
shift += 7;
575
if ((b & 128) == 0) {
576
//Console.WriteLine("Read {0} end {1}", accum, rpointer);
577
return accum;
578
}
579
}
582
public short Short() {
583
return checked((short)Long());
584
}
585
586
public int Int() {
587
return checked((int)Long());
590
public double Double() {
591
return BitConverter.Int64BitsToDouble(Long());
592
}
593
594
public string String() {
595
int l = Int();
596
597
if (l < 0) return null;
598
char[] cb = new char[l];
599
600
for (int i = 0; i < l; i++)
601
cb[i] = (char)ULong();
602
603
return new string(cb);
604
}
605
606
public byte[] Bytes(int k) {
607
byte[] buf = new byte[k];
608
609
for (int i = 0; i < k; i++)
610
buf[i] = Byte();
611
612
return buf;
613
}
614
615
public List<T> RefsL<T>() where T : class {
616
int ct = Int();
617
if (ct < 0) return null;
618
List<T> ret = new List<T>();
619
for (int i = 0; i < ct; i++)
620
ret.Add((T) ObjRef());
621
return ret;
622
}
623
624
public int[] Ints() {
625
int ct = Int();
626
if (ct < 0) return null;
627
int[] ret = new int[ct];
628
for (int i = 0; i < ct; i++) ret[i] = Int();
629
return ret;
630
}
631
632
public string[] Strings() {
633
int ct = Int();
634
if (ct < 0) return null;
635
string[] ret = new string[ct];
636
for (int i = 0; i < ct; i++) ret[i] = String();
637
return ret;
638
}
639
640
public T[] RefsA<T>() where T : class {
641
int ct = Int();
642
if (ct < 0) return null;
643
T[] ret = new T[ct];
644
for (int i = 0; i < ct; i++)
645
ret[i] = (T) ObjRef();
646
return ret;
647
}
648
649
// used from ObjRef only so guaranteed non-null
650
T[] RefsARegister<T>() where T : class {
651
int ct = Int();
652
T[] ret = new T[ct];
653
Register(ret);
654
for (int i = 0; i < ct; i++)
655
ret[i] = (T) ObjRef();
656
return ret;
657
}
658
659
public object ObjRef() {
660
var tag = (SerializationCode)Byte();
661
if (Config.SerTrace)
662
Console.WriteLine("Reading {0} from {1}...", tag, rpointer-1);
663
int i, j;
664
switch(tag) {
665
case SerializationCode.Null:
666
return null;
668
case SerializationCode.ForeignRef:
669
i = Int();
670
j = Int();
671
return unit_map[i].bynum[j];
672
case SerializationCode.SelfRef:
673
i = Int();
674
return unit.bynum[i];
675
case SerializationCode.NewUnitRef:
676
return LoadNewUnit();
678
case SerializationCode.RuntimeUnit:
679
return RuntimeUnit.Thaw(this);
680
case SerializationCode.SubInfo:
681
return SubInfo.Thaw(this);
682
case SerializationCode.STable:
683
return STable.Thaw(this);
684
case SerializationCode.StashEnt:
685
return StashEnt.Thaw(this);
686
case SerializationCode.Rat:
687
return Rat.Thaw(this);
688
case SerializationCode.FatRat:
689
return FatRat.Thaw(this);
690
case SerializationCode.Complex:
691
return Complex.Thaw(this);
692
case SerializationCode.BigInteger:
693
return BigInteger.Thaw(this);
694
case SerializationCode.VarDeque:
695
return VarDeque.Thaw(this);
696
case SerializationCode.VarHash:
697
return VarHash.Thaw(this);
698
case SerializationCode.DispatchEnt:
699
return DispatchEnt.Thaw(this);
700
//case SerializationCode.RxFrame:
701
// return RxFrame.Thaw(this);
702
case SerializationCode.P6how:
703
return P6how.Thaw(this);
704
case SerializationCode.CC:
705
return CC.Thaw(this);
706
case SerializationCode.AltInfo:
707
return AltInfo.Thaw(this);
708
case SerializationCode.Signature:
709
return Signature.Thaw(this);
710
case SerializationCode.Parameter:
711
return Parameter.Thaw(this);
713
case SerializationCode.ReflectObj:
714
return ReflectObj.Thaw(this);
715
case SerializationCode.P6opaque:
716
return P6opaque.Thaw(this);
717
case SerializationCode.Frame:
718
return Frame.Thaw(this);
721
case SerializationCode.String:
722
return Register(String());
723
case SerializationCode.ArrP6any:
724
return RefsARegister<P6any>();
725
case SerializationCode.ArrVariable:
726
return RefsARegister<Variable>();
727
case SerializationCode.ArrString:
728
return RefsARegister<string>();
729
case SerializationCode.ArrCC:
730
return RefsARegister<CC>();
731
case SerializationCode.Boolean:
732
return Register(Byte() != 0);
733
case SerializationCode.Int:
734
return Register(Int());
735
case SerializationCode.Double:
736
return Register(Double());
737
case SerializationCode.Type:
738
return Register(Type.GetType(String(), true));
740
case SerializationCode.SimpleVariable:
741
case SerializationCode.SimpleVariable_1:
742
case SerializationCode.SimpleVariable_2:
743
case SerializationCode.SimpleVariable_3:
744
return SimpleVariable.Thaw(this,
745
(int)tag - (int)SerializationCode.SimpleVariable);
746
case SerializationCode.SubstrLValue:
747
return SubstrLValue.Thaw(this);
748
case SerializationCode.TiedVariable:
749
return TiedVariable.Thaw(this);
750
751
case SerializationCode.SubViviHook:
752
return SubViviHook.Thaw(this);
753
case SerializationCode.ArrayViviHook:
754
return ArrayViviHook.Thaw(this);
755
case SerializationCode.NewArrayViviHook:
756
return NewArrayViviHook.Thaw(this);
757
case SerializationCode.HashViviHook:
758
return HashViviHook.Thaw(this);
759
case SerializationCode.NewHashViviHook:
760
return NewHashViviHook.Thaw(this);
761
762
case SerializationCode.LADNone:
763
return Register(new LADNone());
764
case SerializationCode.LADNull:
765
return Register(new LADNull());
766
case SerializationCode.LADDot:
767
return Register(new LADDot());
768
case SerializationCode.LADDispatcher:
769
return Register(new LADDispatcher());
770
case SerializationCode.LADImp:
771
return Register(new LADImp());
772
case SerializationCode.LADStr:
773
return LADStr.Thaw(this);
774
case SerializationCode.LADStrNoCase:
775
return LADStrNoCase.Thaw(this);
776
case SerializationCode.LADMethod:
777
return LADMethod.Thaw(this);
778
case SerializationCode.LADParam:
779
return LADParam.Thaw(this);
780
case SerializationCode.LADQuant:
781
return LADQuant.Thaw(this);
782
case SerializationCode.LADSequence:
783
return LADSequence.Thaw(this);
784
case SerializationCode.LADAny:
785
return LADAny.Thaw(this);
786
case SerializationCode.LADCC:
787
return LADCC.Thaw(this);
789
throw new ThawException("unexpected object tag " + tag);
793
// call this when thawing any new object
794
internal object Register(object o) {
795
reg.RegisterThawed(unit, o);
799
object LoadNewUnit() {
800
string name = String();
801
if (refed_units == unit_map.Length)
802
Array.Resize(ref unit_map, refed_units * 2);
803
804
SerUnit su = reg.LoadUnit(name);
805
unit_map[refed_units++] = su;
806
807
byte[] hash = Bytes(su.hash.Length);
808
809
for (int i = 0; i < hash.Length; i++)
810
if (hash[i] != su.hash[i])
811
goto badhash;
812
813
int ix = Int();
814
return su.bynum[ix];
815
816
badhash:
817
throw new ThawException(string.Format("Hash mismatch for " +
818
"unit {0} referenced from {1}, wanted {2}, got {3}",
819
su.name, unit.name, Utils.HashToStr(unit.hash),
820
Utils.HashToStr(su.hash)));
821
}
822
}
823
824
// Thrown to indicate data format problems in the serialized stream
825
// Not necessarily bugs; could also indicate stale files, including
826
// cases where the data format is changed and cases where a depended
827
// file was recreated
828
class ThawException : Exception {
829
public ThawException(string s) : base(s) { }
830
public ThawException() : base() { }
832
833
public class ReflectObj : IFreeze {
834
protected virtual object[] GetData() { return new object[0]; }
835
protected virtual void SetData(object[] a) { }
836
void IFreeze.Freeze(FreezeBuffer fb) {
837
fb.Byte((byte)SerializationCode.ReflectObj);
838
fb.String(GetType().AssemblyQualifiedName);
839
fb.Refs(GetData());
840
}
841
842
internal static ReflectObj Thaw(ThawBuffer tb) {
843
string nm = tb.String();
844
if (Backend.cross_level_load)
845
nm = nm.Replace("Run.", "");
846
Type nt = Type.GetType(nm, true);
847
ReflectObj n = (ReflectObj)
848
nt.GetConstructor(new Type[0]).Invoke(new object[0]);
849
tb.Register(n);
850
n.SetData(tb.RefsA<object>());
851
return n;
852
}
853
}