Skip to content

Commit 1a96c95

Browse files
committed
Revert "Remove experimental serialization support which I don't think was ever used"
This reverts commit ed8ef16. We do in fact have a single test which makes sure via JI we can serialize a RubyObject. Whether this really works? Putting back for now.
1 parent 0609902 commit 1a96c95

File tree

4 files changed

+140
-1
lines changed

4 files changed

+140
-1
lines changed

core/src/main/java/org/jruby/BasicObjectStub.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ public static RubyString convertToString(IRubyObject self) {
199199

200200
public static IRubyObject anyToString(IRubyObject self) {
201201
final RubyClass metaClass = getMetaClass(self);
202-
String cname = metaClass.getRealClass().getName(metaClass.runtime.getCurrentContext());
202+
String cname = metaClass.getRealClass().getName();
203203
/* 6:tags 16:addr 1:eos */
204204
return metaClass.runtime.newString("#<" + cname + ":0x" + Integer.toHexString(System.identityHashCode(self)) + '>');
205205
}

core/src/main/java/org/jruby/RubyBasicObject.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2902,6 +2902,61 @@ protected String validateInstanceVariable(IRubyObject name) {
29022902
}).idString();
29032903
}
29042904

2905+
/**
2906+
* Serialization of a Ruby (basic) object involves three steps:
2907+
*
2908+
* <ol>
2909+
* <li>Dump the object itself</li>
2910+
* <li>Dump a String used to load the appropriate Ruby class</li>
2911+
* <li>Dump each variable from varTable in turn</li>
2912+
* </ol>
2913+
*
2914+
* The metaClass field is marked transient since Ruby classes generally will
2915+
* not be able to serialize (since they hold references to method tables,
2916+
* other classes, and potentially thread-, runtime-, or jvm-local state.
2917+
*
2918+
* The varTable field is transient because the layout of the same class may
2919+
* differ across runtimes, since it is determined at runtime based on the
2920+
* order in which variables get assigned for a given class. We serialize
2921+
* entries by name to allow other layouts to work properly.
2922+
*/
2923+
private void writeObject(ObjectOutputStream oos) throws IOException {
2924+
if (metaClass.isSingleton()) {
2925+
throw new IOException("can not serialize singleton object");
2926+
}
2927+
2928+
oos.defaultWriteObject();
2929+
oos.writeUTF(metaClass.getName());
2930+
2931+
metaClass.getVariableTableManager().serializeVariables(this, oos);
2932+
}
2933+
2934+
/**
2935+
* Deserialization proceeds as follows:
2936+
*
2937+
* <ol>
2938+
* <li>Deserialize the object instance. It will have null metaClass and
2939+
* varTable fields.</li>
2940+
* <li>Deserialize the name of the object's class, and retrieve class from a
2941+
* thread-local JRuby instance.</li>
2942+
* <li>Retrieve each variable in turn, re-assigning them by name.</li>
2943+
* </ol>
2944+
*
2945+
* @see RubyBasicObject#writeObject(java.io.ObjectOutputStream)
2946+
*/
2947+
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
2948+
Ruby ruby = Ruby.getThreadLocalRuntime();
2949+
2950+
if (ruby == null) {
2951+
throw new IOException("No thread-local org.jruby.Ruby available; can't deserialize Ruby object. Set with Ruby#setThreadLocalRuntime.");
2952+
}
2953+
2954+
ois.defaultReadObject();
2955+
metaClass = (RubyClass)ruby.getClassFromPath(ois.readUTF());
2956+
2957+
metaClass.getVariableTableManager().deserializeVariables(this, ois);
2958+
}
2959+
29052960
/**
29062961
* Retrieve the call sites for this class.
29072962
*

core/src/main/java/org/jruby/RubyObject.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,68 @@ private static boolean isArrayDig(IRubyObject obj, ObjectSites sites) {
519519
return obj instanceof RubyArray && sites.dig_array.isBuiltin(obj.getMetaClass());
520520
}
521521

522+
/**
523+
* Tries to support Java serialization of Ruby objects. This is
524+
* still experimental and might not work.
525+
*/
526+
// NOTE: Serialization is primarily supported for testing purposes, and there is no general
527+
// guarantee that serialization will work correctly. Specifically, instance variables pointing
528+
// at symbols, threads, modules, classes, and other unserializable types are not detected.
529+
private void writeObject(ObjectOutputStream out) throws IOException {
530+
out.defaultWriteObject();
531+
532+
// write out ivar count followed by name/value pairs
533+
ObjectOutputter outputter = new ObjectOutputter(out);
534+
forEachInstanceVariable(outputter);
535+
outputter.writeCount();
536+
forEachInstanceVariable(outputter);
537+
}
538+
539+
private static class ObjectOutputter implements BiConsumer<String, IRubyObject> {
540+
final ObjectOutputStream out;
541+
int count = 0;
542+
boolean counting = true;
543+
544+
ObjectOutputter(ObjectOutputStream out) {
545+
this.out = out;
546+
}
547+
548+
public void accept(String name, IRubyObject value) {
549+
if (counting) {
550+
count++;
551+
} else {
552+
try {
553+
out.writeObject(name);
554+
out.writeObject(value);
555+
} catch (IOException ioe) {
556+
throwException(ioe);
557+
}
558+
}
559+
}
560+
561+
public void writeCount() {
562+
try {
563+
out.writeInt(count);
564+
counting = false;
565+
} catch (IOException ioe) {
566+
throwException(ioe);
567+
}
568+
}
569+
}
570+
571+
/**
572+
* Tries to support Java unserialization of Ruby objects. This is
573+
* still experimental and might not work.
574+
*/
575+
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
576+
in.defaultReadObject();
577+
// rest in ivar count followed by name/value pairs
578+
int ivarCount = in.readInt();
579+
for (int i = 0; i < ivarCount; i++) {
580+
setInstanceVariable((String)in.readObject(), (IRubyObject)in.readObject());
581+
}
582+
}
583+
522584
private static ObjectSites sites(ThreadContext context) {
523585
return context.sites.Object;
524586
}

core/src/main/java/org/jruby/runtime/ivars/VariableTableManager.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,28 @@ public boolean hasInstanceVariables(RubyBasicObject object) {
482482
return false;
483483
}
484484

485+
public void serializeVariables(RubyBasicObject object, ObjectOutputStream oos) throws IOException {
486+
if (object.varTable != null) {
487+
Map<String, VariableAccessor> accessors = getVariableAccessorsForRead();
488+
oos.writeInt(accessors.size());
489+
for (VariableAccessor accessor : accessors.values()) {
490+
oos.writeUTF(RubyBasicObject.ERR_INSECURE_SET_INST_VAR);
491+
oos.writeObject(accessor.get(object));
492+
}
493+
} else {
494+
oos.writeInt(0);
495+
}
496+
}
497+
498+
public void deserializeVariables(RubyBasicObject object, ObjectInputStream ois) throws IOException, ClassNotFoundException {
499+
int varCount = ois.readInt();
500+
for (int i = 0; i < varCount; i++) {
501+
String name = ois.readUTF();
502+
Object value = ois.readObject();
503+
getVariableAccessorForWrite(name).set(object, value);
504+
}
505+
}
506+
485507
public Object clearVariable(RubyBasicObject object, String name) {
486508
synchronized(object) {
487509
Object value = getVariableAccessorForRead(name).get(object);

0 commit comments

Comments
 (0)