java - Generics and ReadObject -
i have simple server uses generics , object serialization. (t input format, u output format). simplified version deals input shown below:
public class server <t, u> implements runnable { @override public void run () { try (objectinputstream inreader = new objectinputstream (this.connection.getinputstream ())) { t lastobj; while (true) { lastobj = (t) inreader.readobject (); system.out.println (lastobj.getclass ().getname ()); if (null != lastobj) { this.acceptmessage (lastobj); } } catch (ioexception | classnotfoundexception ex) { logger.getlogger (this.getclass ().getname ()).log (level.severe, ex.getmessage (), ex); } } }
if start server
server <integer, string> thisserver = new server ();
then expect accept integer objects , return strings output.
however, using simple client read system.in testing , sending strings server. surprise, server accepted input. make sure accepting object wasn't of type t added line echo out class last object was.
system.out.println (lastobj.getclass ().getname ());
this did in fact output java.lang.string.
this totally unexpected. thought generics supposed allow pass objects of type wasn't specified in class without having cast objects? cast t doesn't seem have effect either.
this means in theory attack server (or consumer) feeding java objects of type wasn't expecting. whilst doesn't have super-robust (because it's academic project , not production software) think knowing object got readobject wasn't 1 wanted can deal important.
i tried adding following, got flagged compile time error.
if (lastobj instanceof t) { }
how handle correctly?
as others have pointed out, issue related type erasure. @ runtime, t
has been erased upper bound, object
.
when cast t
, that's known unchecked cast because doesn't exist @ runtime. instead, other casts have been inserted compiler in places instances of t
assigned reified type integer
. when run
consumes unexpected type string
, jvm can't tell difference, , doesn't fail fast. if there method t getlastobject
, caller of method might fail instead:
server<integer, string> thisserver = ...; thisserver.run(); // consumes string, doesn't fail integer = thisserver.getlastobject(); // classcastexception thrown here
the workaround provide server
class<t>
object representing type of object consumed , use cast
method:
public class server <t, u> implements runnable { private final class<t> readobjecttype; public server(final class<t> readobjecttype) { this.readobjecttype = readobjecttype; } @override public void run () { try (objectinputstream inreader = new objectinputstream (this.connection.getinputstream ())) { t lastobj; while (true) { lastobj = readobjecttype.cast(inreader.readobject()); system.out.println (lastobj.getclass ().getname ()); if (null != lastobj) { this.acceptmessage (lastobj); } } catch (ioexception | classnotfoundexception ex) { logger.getlogger (this.getclass ().getname ()).log (level.severe, ex.getmessage (), ex); } } }
readobjecttype.cast(inreader.readobject())
fail fast when wrong type of object has been read.