Initial checkin.
authorFredrik Tolf <fredrik@dolda2000.com>
Sun, 14 Sep 2014 03:03:03 +0000 (05:03 +0200)
committerFredrik Tolf <fredrik@dolda2000.com>
Sun, 14 Sep 2014 03:03:03 +0000 (05:03 +0200)
.gitignore [new file with mode: 0644]
classfile/binfmt.py [new file with mode: 0644]
classfile/file.py [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..0d20b64
--- /dev/null
@@ -0,0 +1 @@
+*.pyc
diff --git a/classfile/binfmt.py b/classfile/binfmt.py
new file mode 100644 (file)
index 0000000..57671f9
--- /dev/null
@@ -0,0 +1,297 @@
+from struct import pack, unpack, calcsize
+
+class fmterror(Exception):
+    pass
+
+class eomerror(fmterror):
+    pass
+
+def mutf8dec(bs):
+    ret = ""
+    i = 0
+    while i < len(bs):
+        b = bs[i]
+        i += 1
+        if b & 0x80 == 0:
+            ret += chr(b)
+        else:
+            c = 0
+            while (c < 7) and (b & (1 << (6 - c))):
+                c += 1
+            if c == 0 or c == 7: raise fmterror("invalid utf8 start-byte")
+            iacc = acc = b & ((1 << (6 - c)) - 1)
+            ic = c
+            while c > 0:
+                if i >= len(bs): raise fmterror("unterminated utf8 compound")
+                b = bs[i]
+                i += 1
+                if b & 0xc0 != 0x80: raise fmterror("invalid utf8 continuation byte")
+                acc = (acc << 6) | bs & 0x3f
+                c -= 1
+            if iacc == 0 and ic != 2 and acc != 0: raise fmterror("invalid utf8 compound")
+            ret += chr(acc)
+    return ret
+
+def mutf8enc(cs):
+    ret = bytearray()
+    for c in cs:
+        c = ord(c)
+        if c == 0:
+            ret.extend(b"\xc0\x80")
+        elif 1 <= c < 128:
+            ret.append(c)
+        elif 128 <= c < 2048:
+            ret.append(0xc0 | ((c & 0x7c0) >> 6))
+            ret.append(0x80 |  (c & 0x03f))
+        elif 2048 <= c < 65536:
+            ret.append(0xe0 | ((c & 0xf000) >> 12))
+            ret.append(0x80 | ((c & 0x0fc0) >> 6))
+            ret.append(0x80 |  (c & 0x003f))
+        else:
+            raise fmterror("non-BMP unicode not supported by Java")
+    return bytes(ret)
+
+class decoder(object):
+    def destruct(self, fmt):
+        return unpack(fmt, self.splice(calcsize(fmt)))
+
+    def skip(self, ln):
+        self.splice(ln)
+
+    def int8(self):
+        return self.destruct(">b")[0]
+    def uint8(self):
+        return self.destruct(">B")[0]
+    def int16(self):
+        return self.destruct(">h")[0]
+    def uint16(self):
+        return self.destruct(">H")[0]
+    def int32(self):
+        return self.destruct(">i")[0]
+    def uint32(self):
+        return self.destruct(">I")[0]
+    def int64(self):
+        return self.destruct(">q")[0]
+    def uint64(self):
+        return self.destruct(">Q")[0]
+    def float32(self):
+        return self.destruct(">f")[0]
+    def float64(self):
+        return self.destruct(">d")[0]
+
+class decstream(decoder):
+    def __init__(self, bk):
+        self.bk = bk
+        self.buf = bytearray()
+
+    def eom(self):
+        if len(self.buf) > 0:
+            return False
+        ret = self.bk.read(1024)
+        if ret == b"":
+            return True
+        self.buf.extend(ret)
+        return False
+
+    def splice(self, ln=-1):
+        buf = self.buf
+        if ln < 0:
+            while True:
+                ret = self.bk.read()
+                if ret == b"":
+                    self.buf = bytearray()
+                    return bytes(buf)
+                buf.extend(ret)
+        else:
+            while len(buf) < ln:
+                rl = max(ln - len(buf), 1024)
+                ret = self.bk.read(rl)
+                if ret == b"":
+                    raise eomerror("unexpected end-of-file")
+                buf.extend(ret)
+            self.buf = buf[ln:]
+            return bytes(buf[:ln])
+
+    def skip(self, ln):
+        if ln < len(self.buf):
+            self.buf = self.buf[ln:]
+        else:
+            ln -= len(self.buf)
+            self.buf = bytearray()
+            if hasattr(self.bk, "seek"):
+                self.bk.seek(ln - 1, 1)
+                if len(self.bk.read(1)) != 1:
+                    raise eomerror("unexpected end-of-file")
+            else:
+                while ln > 0:
+                    r = self.bk.read(ln)
+                    if r == b"":
+                        raise eomerror("unexpected end-of-file")
+                    ln -= len(r)
+
+    def str(self):
+        buf = self.buf
+        p = 0
+        while True:
+            p2 = buf.find(b'\0', p)
+            if p2 > 0:
+                self.buf = buf[p2 + 1:]
+                return str(buf[:p2], "utf-8")
+            ret = self.bk.read(1024)
+            if ret == b"":
+                if len(buf) == 0:
+                    raise eomerror("unexpected end-of-file")
+                raise fmterror("no string terminator found")
+            p = len(buf)
+            buf.extend(ret)
+
+    def close(self):
+        self.bk.close()
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *excinfo):
+        self.close()
+        return False
+
+class decbuf(decoder):
+    def __init__(self, data):
+        self.data = data
+        self.offset = 0
+
+    def __len__(self):
+        return len(self.data) - self.offset
+
+    def eom(self):
+        return self.offset >= len(self.data)
+
+    def splice(self, ln=-1):
+        if ln < 0:
+            ret = self.data[self.offset:]
+            self.offset = len(self.data)
+            return ret
+        else:
+            if self.offset + ln > len(self.data):
+                raise eomerror("out of data to decode")
+            ret = self.data[self.offset:self.offset + ln]
+            self.offset += ln
+            return ret
+
+    def str(self):
+        p = self.data.find(b'\0', self.offset)
+        if p < 0:
+            if self.offset == len(self.data):
+                raise eomerror("out of data to decode")
+            raise fmterror("no string terminator found")
+        ret = str(self.data[self.offset:p], "utf-8")
+        self.offset = p + 1
+        return str(ret)
+
+class encoder(object):
+    def enstruct(self, fmt, *args):
+        self.extend(pack(fmt, *args))
+        return self
+
+    def int8(self, val):
+        self.enstruct(">b", val)
+        return self
+    def uint8(self, val):
+        self.enstruct(">B", val)
+        return self
+    def int16(self, val):
+        self.enstruct(">h", val)
+        return self
+    def uint16(self, val):
+        self.enstruct(">H", val)
+        return self
+    def int32(self, val):
+        self.enstruct(">i", val)
+        return self
+    def uint32(self, val):
+        self.enstruct(">I", val)
+        return self
+    def int64(self, val):
+        self.enstruct(">q", val)
+        return self
+    def uint64(self, val):
+        self.enstruct(">Q", val)
+        return self
+    def float32(self, val):
+        self.enstruct(">f", val)
+        return self
+    def float64(self, val):
+        self.enstruct(">d", val)
+        return self
+
+    def str(self, val):
+        if val.find('\0') >= 0:
+            raise ValueError("encoded strings must not contain NULs")
+        self.extend(val.encode("utf-8"))
+        self.extend(b"\0")
+        return self
+
+    def ttol(self, val, term=False):
+        for obj in val:
+            if isinstance(obj, int):
+                if 0 <= obj < 256:
+                    self.uint8(T_UINT8)
+                    self.uint8(obj)
+                elif 0 <= obj < 65536:
+                    self.uint8(T_UINT16)
+                    self.uint16(obj)
+                else:
+                    self.uint8(T_INT)
+                    self.int32(obj)
+            elif isinstance(obj, str):
+                self.uint8(T_STR)
+                self.str(obj)
+            elif isinstance(obj, utils.coord):
+                self.uint8(T_COORD)
+                self.coord(obj)
+            elif isinstance(obj, utils.color):
+                self.uint8(T_COLOR)
+                self.color(obj)
+            elif isinstance(obj, list):
+                self.uint8(T_TTOL)
+                self.ttol(obj, True)
+            elif isinstance(obj, float):
+                self.uint8(T_FLOAT32)
+                self.float32(obj)
+            elif obj is None:
+                self.uint8(T_NIL)
+            elif isinstance(obj, collections.ByteString):
+                self.uint8(T_BYTES)
+                if len(obj) < 128:
+                    self.uint8(len(obj))
+                else:
+                    self.uint8(0x80).int32(len(obj))
+                self.extend(obj)
+            else:
+                raise ValueError("unexpected type in tto-list: %s" % type(obj))
+        if term:
+            self.uint8(T_END)
+        return self
+
+class encstream(encoder):
+    def __init__(self, bk):
+        self.bk = bk
+
+    def extend(self, data):
+        self.bk.write(data)
+        return self
+
+    def close(self):
+        self.bk.close()
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *excinfo):
+        self.close()
+        return False
+
+class encbuf(encoder, bytearray):
+    def extend(self, data):
+        bytearray.extend(self, data)
+        return self
diff --git a/classfile/file.py b/classfile/file.py
new file mode 100644 (file)
index 0000000..47e9103
--- /dev/null
@@ -0,0 +1,720 @@
+import collections
+from . import binfmt
+
+ACC_PUBLIC       = 0x0001
+ACC_PRIVATE      = 0x0002
+ACC_PROTECTED    = 0x0004
+ACC_STATIC       = 0x0008
+ACC_FINAL        = 0x0010
+ACC_SUPER        = 0x0020
+ACC_SYNCHRONIZED = 0x0020
+ACC_VOLATILE     = 0x0040
+ACC_BRIDGE       = 0x0040
+ACC_TRANSIENT    = 0x0080
+ACC_VARARGS      = 0x0080
+ACC_NATIVE       = 0x0100
+ACC_INTERFACE    = 0x0200
+ACC_ABSTRACT     = 0x0400
+ACC_STRICT       = 0x0800
+ACC_SYNTHETIC    = 0x1000
+ACC_ANNOTATION   = 0x2000
+ACC_ENUM         = 0x4000
+
+CONSTANT_Class              = 7
+CONSTANT_Fieldref           = 9
+CONSTANT_Methodref          = 10
+CONSTANT_InterfaceMethodref = 11
+CONSTANT_String             = 8
+CONSTANT_Integer            = 3
+CONSTANT_Float              = 4
+CONSTANT_Long               = 5
+CONSTANT_Double             = 6
+CONSTANT_NameAndType        = 12
+CONSTANT_Utf8               = 1
+CONSTANT_MethodHandle       = 15
+CONSTANT_MethodType         = 16
+CONSTANT_InvokeDynamic      = 18
+
+version = collections.namedtuple("version", ["major", "minor"])
+version.__eq__ = lambda s, o: s.major == o.major and s.minor == o.minor
+version.__ne__ = lambda s, o: s.major != o.major or s.minor != o.minor
+version.__lt__ = lambda s, o: (s.major < o.major) or (s.major == o.major and s.minor < o.minor)
+version.__gt__ = lambda s, o: (s.major > o.major) or (s.major == o.major and s.minor > o.minor)
+version.__le__ = lambda s, o: (s.major < o.major) or (s.major == o.major and s.minor <= o.minor)
+version.__ge__ = lambda s, o: (s.major > o.major) or (s.major == o.major and s.minor >= o.minor)
+version.J5 = version(49, 0)
+version.J6 = version(50, 0)
+version.J7 = version(51, 0)
+version.J8 = version(52, 0)
+
+class constint(object):
+    def __init__(self, val):
+        self.val = val
+    def __hash__(self):
+        return hash(constint) + self.val
+    def __eq__(s, o):
+        return isinstance(o, constint) and o.val == s.val
+class constfloat(object):
+    def __init__(self, val):
+        self.val = val
+    def __hash__(self):
+        return hash(constfloat) + self.val
+    def __eq__(s, o):
+        return isinstance(o, constfloat) and o.val == s.val
+class constlong(object):
+    def __init__(self, val):
+        self.val = val
+    def __hash__(self):
+        return hash(constlong) + self.val
+    def __eq__(s, o):
+        return isinstance(o, constlong) and o.val == s.val
+class constdouble(object):
+    def __init__(self, val):
+        self.val = val
+    def __hash__(self):
+        return hash(constdouble) + self.val
+    def __eq__(s, o):
+        return isinstance(o, constdouble) and o.val == s.val
+
+class conststr(object):
+    def __init__(self, idx):
+        self.idx = idx
+    def __hash__(self):
+        return hash(conststr) + self.idx
+    def __eq__(s, o):
+        return isinstance(o, conststr) and o.idx == s.idx
+
+class classref(object):
+    def __init__(self, nm):
+        self.nm = nm
+    def __hash__(self):
+        return hash(classref) + self.nm
+    def __eq__(s, o):
+        return isinstance(o, classref) and o.nm == s.nm
+
+class sig(object):
+    def __init__(self, nm, tp):
+        self.nm = nm
+        self.tp = tp
+    def __hash__(self):
+        return hash(sig) + self.nm * 31 + self.tp
+    def __eq__(s, o):
+        return isinstance(o, sig) and o.nm == s.nm and o.tp == s.tp
+
+class fieldref(object):
+    def __init__(self, cls, sig):
+        self.cls = cls
+        self.sig = sig
+    def __hash__(self):
+        return hash(fieldref) + self.cls * 31 + self.sig
+    def __eq__(s, o):
+        return isinstance(o, fieldref) and o.cls == s.cls and o.sig == s.sig
+
+class methodref(object):
+    def __init__(self, cls, sig):
+        self.cls = cls
+        self.sig = sig
+    def __hash__(self):
+        return hash(methodref) + self.cls * 31 + self.sig
+    def __eq__(s, o):
+        return isinstance(o, methodref) and o.cls == s.cls and o.sig == s.sig
+
+class imethodref(object):
+    def __init__(self, cls, sig):
+        self.cls = cls
+        self.sig = sig
+    def __hash__(self):
+        return hash(imethodref) + self.cls * 31 + self.sig
+    def __eq__(s, o):
+        return isinstance(o, imethodref) and o.cls == s.cls and o.sig == s.sig
+
+class field(object):
+    def __init__(self, acc, nm, descr):
+        self.acc = acc
+        self.nm = nm
+        self.descr = descr
+        self.const = None
+        self.syn = False
+        self.sig = None
+        self.deprecated = False
+        self.rtann = []
+        self.cpann = []
+        self.attrs = []
+
+class localdef(object):
+    def __init__(self, start, end, nm, descr, reg):
+        self.start = start
+        self.end = end
+        self.nm = nm
+        self.descr = descr
+        self.reg = reg
+
+class code(object):
+    def __init__(self):
+        self.maxstack = 0
+        self.maxlocals = 0
+        self.code = b""
+        self.exctab = []
+        self.lintab = None
+        self.locals = None
+        self.tlocals = None
+        self.attrs = []
+
+class method(object):
+    def __init__(self, acc, nm, descr):
+        self.acc = acc
+        self.nm = nm
+        self.descr = descr
+        self.code = None
+        self.throws = []
+        self.syn = False
+        self.sig = None
+        self.deprecated = False
+        self.rtann = []
+        self.cpann = []
+        self.prtann = None
+        self.pcpann = None
+        self.anndef = None
+        self.attrs = []
+
+class annotation(object):
+    def __init__(self, tp):
+        self.tp = tp
+        self.vals = {}
+
+class innerclass(object):
+    def __init__(self, cls, outer, nm, acc):
+        self.cls = cls
+        self.outer = outer
+        self.nm = nm
+        self.acc = acc
+
+class classfile(object):
+    MAGIC = 0xCAFEBABE
+
+    def __init__(self, ver, access=None):
+        self.ver = ver
+        self.cp = []
+        self.access = access
+        self.this = None
+        self.super = None
+        self.ifaces = []
+        self.fields = []
+        self.methods = []
+        self.srcfile = None
+        self.innerclasses = []
+        self.enclosingmethod = None
+        self.syn = False
+        self.sig = None
+        self.deprecated = False
+        self.rtann = []
+        self.cpann = []
+        self.attrs = []
+
+    def loadconstant(self, buf):
+        t = buf.uint8()
+        if t == CONSTANT_Utf8:
+            return binfmt.mutf8dec(buf.splice(buf.uint16())), False
+        elif t == CONSTANT_Class:
+            return classref(buf.uint16()), False
+        elif t == CONSTANT_String:
+            return conststr(buf.uint16()), False
+        elif t == CONSTANT_Integer:
+            return constint(buf.int32()), False
+        elif t == CONSTANT_Float:
+            return constfloat(buf.float32()), False
+        elif t == CONSTANT_Long:
+            return constlong(buf.int64()), True
+        elif t == CONSTANT_Double:
+            return constdouble(buf.float64()), True
+        elif t == CONSTANT_Fieldref:
+            return fieldref(buf.uint16(), buf.uint16()), False
+        elif t == CONSTANT_Methodref:
+            return methodref(buf.uint16(), buf.uint16()), False
+        elif t == CONSTANT_InterfaceMethodref:
+            return imethodref(buf.uint16(), buf.uint16()), False
+        elif t == CONSTANT_NameAndType:
+            return sig(buf.uint16(), buf.uint16()), False
+        else:
+            raise binfmt.fmterror("unknown constant tag: " + str(t))
+
+    def saveconstant(self, buf, const):
+        if isinstance(const, str):
+            buf.uint8(CONSTANT_Utf8).extend(binfmt.mutf8enc(const))
+        elif isinstance(const, classref):
+            buf.uint8(CONSTANT_Class).uint16(const.nm)
+        elif isinstance(const, conststr):
+            buf.uint8(CONSTANT_String).uint16(const.idx)
+        elif isinstance(const, constint):
+            buf.uint8(CONSTANT_Integer).int32(const.val)
+        elif isinstance(const, constfloat):
+            buf.uint8(CONSTANT_Float).float32(const.val)
+        elif isinstance(const, constlong):
+            buf.uint8(CONSTANT_Long).int64(const.val)
+        elif isinstance(const, constdouble):
+            buf.uint8(CONSTANT_Double).float64(const.val)
+        elif isinstance(const, fieldref):
+            buf.uint8(CONSTANT_Fieldref).uint16(const.cls).uint16(const.sig)
+        elif isinstance(const, methodref):
+            buf.uint8(CONSTANT_Methodref).uint16(const.cls).uint16(const.sig)
+        elif isinstance(const, imethodref):
+            buf.uint8(CONSTANT_InterfaceMethodref).uint16(const.cls).uint16(const.sig)
+        elif isinstance(const, sig):
+            buf.uint8(CONSTANT_NameAndType).uint16(const.nm).uint16(const.tp)
+        else:
+            raise Exception("unexpected object type in constant pool: " + const)
+
+    def checkcp(self, idx, tp):
+        return 0 <= idx < len(self.cp) and isinstance(self.cp[idx], tp)
+
+    def intern(self, const, new=Exception):
+        for i, cur in enumerate(self.cp):
+            if cur == const:
+                return i
+        if new == Exception:
+            raise Exception("constant not present in pool: " + const)
+        if new:
+            self.cp.append(const)
+            return len(self.cp) - 1
+        else:
+            return None
+
+    def loadattr(self, buf):
+        nm = buf.uint16()
+        if not self.checkcp(nm, str):
+            raise binfmt.fmterror("invalid attribute name reference")
+        return nm, binfmt.decbuf(buf.splice(buf.uint32()))
+
+    def saveattrs(self, buf, attrs):
+        buf.uint16(len(attrs))
+        for nm, data in attrs:
+            buf.uint16(nm).uint32(len(data)).extend(data)
+
+    def loadannval(self, buf):
+        t = chr(buf.uint8())
+        if t in "BCDFIJSZs":
+            return buf.uint16()
+        elif t == "e":
+            return (buf.uint16(), buf.uint16())
+        elif t == "c":
+            return classref(buf.uint16()) # XXX, but meh
+        elif t == "@":
+            return loadannotation(buf)
+        elif t == "[":
+            return [self.loadannval(buf) for i in range(buf.uint16())]
+        else:
+            raise binfmt.fmterror("unknown annotation-value type tag: " + t)
+
+    def saveannval(self, buf, val):
+        if isinstance(val, int):
+            const = self.cp[val]
+            if isinstance(const, str):
+                buf.uint8(ord('s')).uint16(val)
+            else:
+                raise Exception("unexpected constant type in annotation value: " + const)
+        elif isinstance(val, tuple) and len(val) == 2:
+            buf.uint8(ord('e')).uint16(val[0]).uint16(val[1])
+        elif isinstance(val, classref):
+            buf.uint8(ord('c')).uint16(val.nm)
+        elif isinstance(val, annotation):
+            buf.uint8(ord('@'))
+            saveannotation(buf, val)
+        elif isinstance(val, list):
+            buf.uint8(ord('['))
+            for sval in val: self.saveannval(buf, sval)
+        else:
+            raise Exception("unexpected annotation value type: " + val)
+
+    def loadannotation(self, buf):
+        tp = buf.uint16()
+        if not self.checkcp(tp, str):
+            raise binfmt.fmterror("invalid annotation type reference")
+        ret = annotation(tp)
+        nval = buf.uint16()
+        for i in range(nval):
+            nm = buf.uint16()
+            if not self.checkcp(nm, str):
+                raise binfmt.fmterror("invalid annotation-value name reference")
+            ret.vals[nm] = self.loadannval(buf)
+        return ret
+
+    def saveannotation(self, buf, ann):
+        buf.uint16(ann.tp)
+        buf.uint16(len(ann.vals))
+        for key, val in ann.vals.items():
+            buf.uint16(key)
+            self.saveannval(buf, val)
+
+    def loadfield(self, buf):
+        acc = buf.uint16()
+        nm = buf.uint16()
+        if not self.checkcp(nm, str):
+            raise binfmt.fmterror("invalid field name reference")
+        descr = buf.uint16()
+        if not self.checkcp(descr, str):
+            raise binfmt.fmterror("invalid field descriptor reference")
+        ret = field(acc, nm, descr)
+        nattr = buf.uint16()
+        for i in range(nattr):
+            nm, data = self.loadattr(buf)
+            pnm = self.cp[nm]
+            if pnm == "ConstantValue":
+                ret.const = data.uint16()
+            elif pnm == "Synthetic":
+                ret.syn = True
+            elif pnm == "Signature":
+                ret.sig = data.uint16()
+            elif pnm == "Deprecated":
+                ret.deprecated = True
+            elif pnm == "RuntimeVisibleAnnotations":
+                for o in range(data.uint16()):
+                    ret.rtann.append(self.loadannotation(data))
+            elif pnm == "RuntimeInvisibleAnnotations":
+                for o in range(data.uint16()):
+                    ret.cpann.append(self.loadannotation(data))
+            else:
+                ret.attrs.append((nm, data.splice()))
+        return ret
+
+    def savefield(self, buf, field):
+        buf.uint16(field.acc)
+        buf.uint16(field.nm).uint16(field.descr)
+        attrs = list(field.attrs)
+        enc = binfmt.encbuf
+        if field.const is not None:
+            attrs.append((self.intern("ConstantValue"), enc().uint16(field.const)))
+        if field.syn:
+            attrs.append((self.intern("Synthetic"), b""))
+        if field.sig is not None:
+            attrs.append((self.intern("Signature"), enc().uint16(field.sig)))
+        if field.deprecated:
+            attrs.append((self.intern("Deprecated"), b""))
+        if len(field.rtann) > 0:
+            data = enc()
+            data.uint16(len(field.rtann))
+            for ann in field.rtann: self.saveannotation(data, ann)
+            attrs.append((self.intern("RuntimeVisibleAnnotations"), data))
+        if len(field.cpann) > 0:
+            data = enc()
+            data.uint16(len(field.cpann))
+            for ann in field.cpann: self.saveannotation(data, ann)
+            attrs.append((self.intern("RuntimeInvisibleAnnotations"), data))
+        self.saveattrs(buf, attrs)
+
+    def loadcode(self, buf):
+        ret = code()
+        ret.maxstack = buf.uint16()
+        ret.maxlocals = buf.uint16()
+        ret.code = buf.splice(buf.uint32())
+        for i in range(buf.uint16()):
+            estart = buf.uint16()
+            eend = buf.uint16()
+            ehnd = buf.uint16()
+            ctp = buf.uint16()
+            if not (ctp == 0 or self.checkcp(ctp, classref)):
+                raise binfmt.fmterror("invalid exception-catch reference")
+            ret.exctab.append((estart, eend, ehnd, ctp))
+        nattr = buf.uint16()
+        for i in range(nattr):
+            nm, data = self.loadattr(buf)
+            pnm = self.cp[nm]
+            if pnm == "LineNumberTable":
+                lintab = []
+                for o in range(data.uint16()):
+                    pc = data.uint16()
+                    ln = data.uint16()
+                    lintab.append((pc, ln))
+                ret.lintab = lintab
+            elif pnm in ("LocalVariableTable", "LocalVariableTypeTable"):
+                locals = []
+                for o in range(data.uint16()):
+                    start = data.uint16()
+                    ln = data.uint16()
+                    nm = data.uint16()
+                    descr = data.uint16()
+                    reg = data.uint16()
+                    if not self.checkcp(nm, str):
+                        raise binfmt.fmterror("invalid local variable name reference")
+                    if not self.checkcp(descr, str):
+                        raise binfmt.fmterror("invalid local variable descriptor reference")
+                    locals.append(localdef(start, start + ln, nm, descr, reg))
+                if nm == "LocalVariableTypeTable":
+                    ret.tlocals = locals
+                else:
+                    ret.locals = locals
+            else:
+                ret.attrs.append((nm, data.splice()))
+        return ret
+
+    def savecode(self, buf, code):
+        buf.uint16(code.maxstack).uint16(code.maxlocals)
+        buf.uint32(len(code.code)).extend(code.code)
+        buf.uint16(len(code.exctab))
+        for estart, eend, ehnd, ctp in code.exctab:
+            buf.uint16(estart).uint16(eend).uint16(ehnd).uint16(ctp)
+        attrs = list(code.attrs)
+        enc = binfmt.encbuf
+        if code.lintab is not None:
+            data = enc()
+            data.uint16(len(code.lintab))
+            for pc, ln in code.lintab:
+                data.uint16(pc).uint16(ln)
+            attrs.append((self.intern("LineNumberTable"), data))
+        def savelocals(ltab):
+            data = enc()
+            data.uint16(len(ltab))
+            for local in ltab:
+                data.uint16(local.start).uint16(local.end - local.start).uint16(local.nm).uint16(local.descr).uint16(local.reg)
+            return data
+        if code.locals is not None:
+            attrs.append((self.intern("LocalVariableTable"), savelocals(code.locals)))
+        if code.tlocals is not None:
+            attrs.append((self.intern("LocalVariableTypeTable"), savelocals(code.tlocals)))
+        self.saveattrs(buf, attrs)
+
+    def loadmethod(self, buf):
+        acc = buf.uint16()
+        nm = buf.uint16()
+        if not self.checkcp(nm, str):
+            raise binfmt.fmterror("invalid field name reference")
+        descr = buf.uint16()
+        if not self.checkcp(descr, str):
+            raise binfmt.fmterror("invalid field descriptor reference")
+        ret = method(acc, nm, descr)
+        nattr = buf.uint16()
+        for i in range(nattr):
+            nm, data = self.loadattr(buf)
+            pnm = self.cp[nm]
+            if pnm == "Code":
+                ret.code = self.loadcode(data)
+            elif pnm == "Exceptions":
+                for o in range(data.uint16()):
+                    eref = data.uint16()
+                    if not self.checkcp(eref, classref):
+                        raise binfmt.fmterror("invalid exception reference")
+                    ret.throws.append(eref)
+            elif pnm == "Synthetic":
+                ret.syn = True
+            elif pnm == "Signature":
+                ret.sig = data.uint16()
+            elif pnm == "Deprecated":
+                ret.deprecated = True
+            elif pnm == "RuntimeVisibleAnnotations":
+                for o in range(data.uint16()):
+                    ret.rtann.append(self.loadannotation(data))
+            elif pnm == "RuntimeInvisibleAnnotations":
+                for o in range(data.uint16()):
+                    ret.cpann.append(self.loadannotation(data))
+            elif pnm == "RuntimeVisibleParameterAnnotations":
+                ret.prtann = []
+                for o in range(data.uint8()):
+                    abuf = []
+                    for u in range(data.uint16()):
+                        abuf.append(self.loadannotation(data))
+                    ret.prtann.append(abuf)
+            elif pnm == "RuntimeInvisibleParameterAnnotations":
+                ret.pcpann = []
+                for o in range(data.uint8()):
+                    abuf = []
+                    for u in range(data.uint16()):
+                        abuf.append(self.loadannotation(data))
+                    ret.pcpann.append(abuf)
+            elif pnm == "AnnotationDefault":
+                ret.anndef = self.loadannval(data)
+            else:
+                ret.attrs.append((nm, data.splice()))
+        return ret
+
+    def savemethod(self, buf, method):
+        buf.uint16(method.acc)
+        buf.uint16(method.nm).uint16(method.descr)
+        attrs = list(method.attrs)
+        enc = binfmt.encbuf
+        if method.code:
+            data = enc()
+            self.savecode(data, method.code)
+            attrs.append((self.intern("Code"), data))
+        if len(method.throws) > 0:
+            data = enc()
+            data.uint16(len(method.throws))
+            for eref in method.throws: data.uint16(eref)
+            attrs.append((self.intern("Exceptions"), data))
+        if method.syn:
+            attrs.append((self.intern("Synthetic"), b""))
+        if method.sig is not None:
+            attrs.append((self.intern("Signature"), enc().uint16(method.sig)))
+        if method.deprecated:
+            attrs.append((self.intern("Deprecated"), b""))
+        if len(method.rtann) > 0:
+            data = enc()
+            data.uint16(len(method.rtann))
+            for ann in method.rtann: self.saveannotation(data, ann)
+            attrs.append((self.intern("RuntimeVisibleAnnotations"), data))
+        if len(method.cpann) > 0:
+            data = enc()
+            data.uint16(len(method.cpann))
+            for ann in method.cpann: self.saveannotation(data, ann)
+            attrs.append((self.intern("RuntimeInvisibleAnnotations"), data))
+        if method.prtann is not None:
+            data = enc()
+            data.uint8(len(method.prtann))
+            for par in method.prtann:
+                buf.uint16(len(par))
+                for ann in par: self.saveannotation(data, ann)
+            attrs.append((self.intern("RuntimeVisibleParameterAnnotations"), data))
+        if method.pcpann is not None:
+            data = enc()
+            data.uint8(len(method.pcpann))
+            for par in method.pcpann:
+                buf.uint16(len(par))
+                for ann in par: self.saveannotation(data, ann)
+            attrs.append((self.intern("RuntimeInvisibleParameterAnnotations"), data))
+        if method.anndef is not None:
+            data = enc()
+            self.saveannval(data, method.anndef)
+            attrs.append((self.intern("AnnotationDefault"), data))
+        self.saveattrs(buf, attrs)
+
+    @classmethod
+    def load(cls, fp):
+        buf = binfmt.decstream(fp)
+        if buf.uint32() != cls.MAGIC:
+            raise binfmt.fmterror("invalid magic number")
+        minor, major = buf.uint16(), buf.uint16()
+        self = cls(version(major, minor))
+
+        cplen = buf.uint16()
+        if cplen < 1:
+            raise binfmt.fmterror("invalid constant-pool length")
+        self.cp.append(None)
+        while len(self.cp) < cplen:
+            loaded, dbl = self.loadconstant(buf)
+            self.cp.append(loaded)
+            if dbl:
+                self.cp.append(None)
+
+        self.acc = buf.uint16()
+        self.this = buf.uint16()
+        self.super = buf.uint16()
+        if not self.checkcp(self.this, classref):
+            raise binfmt.fmterror("invalid class name reference")
+        if not self.checkcp(self.super, classref):
+            raise binfmt.fmterror("invalid super-class reference")
+        iflen = buf.uint16()
+        while len(self.ifaces) < iflen:
+            iref = buf.uint16()
+            if not self.checkcp(iref, classref):
+                raise binfmt.fmterror("invalid interface reference")
+            self.ifaces.append(iref)
+
+        nfields = buf.uint16()
+        while len(self.fields) < nfields:
+            self.fields.append(self.loadfield(buf))
+        nmethods = buf.uint16()
+        while len(self.methods) < nmethods:
+            self.methods.append(self.loadmethod(buf))
+
+        nattrs = buf.uint16()
+        for i in range(nattrs):
+            nm, data = self.loadattr(buf)
+            pnm = self.cp[nm]
+            if pnm == "SourceFile":
+                self.srcfile = data.uint16()
+            elif pnm == "Signature":
+                self.sig = data.uint16()
+            elif pnm == "Synthetic":
+                self.syn = True
+            elif pnm == "Deprecated":
+                self.deprecated = True
+            elif pnm == "InnerClasses":
+                for o in range(data.uint16()):
+                    cref = data.uint16()
+                    outer = data.uint16()
+                    cnm = data.uint16()
+                    acc = data.uint16()
+                    if not self.checkcp(cref, classref):
+                        raise binfmt.fmterror("invalid inner-class reference")
+                    if not (outer == 0 or self.checkcp(outer, classref)):
+                        raise binfmt.fmterror("invalid inner-class outer reference")
+                    if not (cnm == 0 or self.checkcp(cnm, str)):
+                        raise binfmt.fmterror("invalid inner-class name reference")
+                    self.innerclasses.append(innerclass(cref, outer, cnm, acc))
+            elif pnm == "EnclosingMethod":
+                self.enclosingmethod = (data.uint16(), data.uint16())
+                if not self.checkcp(self.enclosingmethod[0], classref):
+                    raise binfmt.fmterror("invalid enclosing-method class reference")
+                if not (self.enclosingmethod[1] == 0 or self.checkcp(self.enclosingmethod[1], sig)):
+                    raise binfmt.fmterror("invalid enclosing-method method reference")
+            elif pnm == "RuntimeVisibleAnnotations":
+                for o in range(data.uint16()):
+                    self.rtann.append(self.loadannotation(data))
+            elif pnm == "RuntimeInvisibleAnnotations":
+                for o in range(data.uint16()):
+                    self.cpann.append(self.loadannotation(data))
+            else:
+                self.attrs.append((nm, data.splice()))
+
+        return self
+
+    def _save(self, buf):
+        buf.uint32(self.MAGIC)
+        buf.uint16(self.ver.minor).uint16(self.ver.major)
+
+        buf.uint16(len(self.cp) + 1)
+        for const in self.cp:
+            if const is not None:
+                self.saveconstant(buf, const)
+
+        buf.uint16(self.acc)
+        buf.uint16(self.this).uint16(self.super)
+        buf.uint16(len(self.ifaces))
+        for iref in self.ifaces: buf.uint16(iref)
+
+        buf.uint16(len(self.fields))
+        for field in self.fields:
+            self.savefield(buf, field)
+
+        buf.uint16(len(self.methods))
+        for method in self.methods:
+            self.savemethod(buf, method)
+
+        enc = binfmt.encbuf
+        attrs = list(self.attrs)
+        if self.srcfile is not None:
+            attrs.append((self.intern("SourceFile"), enc().uint16(self.srcfile)))
+        if self.syn:
+            attrs.append((self.intern("Synthetic"), b""))
+        if self.deprecated:
+            attrs.append((self.intern("Deprecated"), b""))
+        if self.sig is not None:
+            attrs.append((self.intern("Signature"), enc().uint16(self.sig)))
+        if len(self.innerclasses) > 0:
+            data = enc()
+            data.uint16(len(self.innerclasses))
+            for inner in self.innerclasses: data.uint16(inner.cls).uint16(inner.outer).uint16(inner.nm).uint16(inner.acc)
+            attrs.append((self.intern("InnerClasses"), data))
+        if self.enclosingmethod is not None:
+            attrs.append((self.intern("EnclosingMethod", enc().uint16(self.enclosingmethod[0]).uint16(self.enclosingmethod[1]))))
+        if len(self.rtann) > 0:
+            data = enc()
+            data.uint16(len(self.rtann))
+            for ann in self.rtann: self.saveannotation(data, ann)
+            attrs.append((self.intern("RuntimeVisibleAnnotations"), data))
+        if len(self.cpann) > 0:
+            data = enc()
+            data.uint16(len(self.cpann))
+            for ann in self.cpann: self.saveannotation(data, ann)
+            attrs.append((self.intern("RuntimeInvisibleAnnotations"), data))
+        self.saveattrs(buf, attrs)
+
+    def save(self, fp):
+        return self._save(binfmt.encstream(fp))
+
+    @classmethod
+    def fromfile(cls, fn):
+        with open(fn, "rb") as fp:
+            return cls.load(fp)
+
+    def tofile(self, fn):
+        with open(fn, "wb") as fp:
+            return self.save(fp)