hs-java-0.1: JVM/ClassFile.hs
{-# LANGUAGE RecordWildCards, BangPatterns #-}
-- | This module declares (low-level) data types for Java .class files
-- structures, and Binary instances to read/write them.
module JVM.ClassFile
(ClassFile (..),
CpInfo (..),
FieldInfo (..),
MethodInfo (..),
AttributeInfo (..),
FieldType (..),
FieldSignature, MethodSignature (..), ReturnSignature (..),
ArgumentSignature (..)
)
where
import Control.Monad
import Control.Applicative
import Data.Binary
import Data.Binary.IEEE754
import Data.Binary.Get
import Data.Binary.Put
import Data.Char
import Data.List
import qualified Data.ByteString.Lazy as B
-- | Read one-byte Char
getChar8 :: Get Char
getChar8 = do
x <- getWord8
return $ chr (fromIntegral x)
-- | Generic .class file format
data ClassFile = ClassFile {
magic :: Word32, -- ^ Magic value: 0xCAFEBABE
minorVersion :: Word16,
majorVersion :: Word16,
constsPoolSize :: Word16, -- ^ Number of items in constants pool
constsPool :: [CpInfo], -- ^ Constants pool itself
accessFlags :: Word16, -- ^ See @JVM.Types.AccessFlag@
thisClass :: Word16, -- ^ Constants pool item index for this class
superClass :: Word16, -- ^ --/-- for super class, zero for java.lang.Object
interfacesCount :: Word16, -- ^ Number of implemented interfaces
interfaces :: [Word16], -- ^ Constants pool item indexes for implemented interfaces
classFieldsCount :: Word16, -- ^ Number of class fileds
classFields :: [FieldInfo], -- ^ Class fields
classMethodsCount :: Word16, -- ^ Number of class methods
classMethods :: [MethodInfo], -- ^ Class methods
classAttributesCount :: Word16, -- ^ Number of class attributes
classAttributes :: [AttributeInfo] -- ^ Class attributes
}
deriving (Eq, Show)
instance Binary ClassFile where
put (ClassFile {..}) = do
put magic
put minorVersion
put majorVersion
put constsPoolSize
forM_ constsPool put
put accessFlags
put thisClass
put superClass
put interfacesCount
forM_ interfaces put
put classFieldsCount
forM_ classFields put
put classMethodsCount
forM_ classMethods put
put classAttributesCount
forM_ classAttributes put
get = do
magic <- get
minor <- get
major <- get
poolsize <- get
pool <- replicateM (fromIntegral poolsize - 1) get
af <- get
this <- get
super <- get
interfacesCount <- get
ifaces <- replicateM (fromIntegral interfacesCount) get
classFieldsCount <- get
classFields <- replicateM (fromIntegral classFieldsCount) get
classMethodsCount <- get
classMethods <- replicateM (fromIntegral classMethodsCount) get
asCount <- get
as <- replicateM (fromIntegral $ asCount) get
return $ ClassFile magic minor major poolsize pool af this super
interfacesCount ifaces classFieldsCount classFields classMethodsCount classMethods asCount as
-- | Field signature format
data FieldType =
SignedByte -- ^ B
| CharByte -- ^ C
| DoubleType -- ^ D
| FloatType -- ^ F
| IntType -- ^ I
| LongInt -- ^ J
| ShortInt -- ^ S
| BoolType -- ^ Z
| ObjectType String -- ^ L @{class name}@
| Array (Maybe Int) FieldType -- ^ @[{type}@
deriving (Eq)
instance Show FieldType where
show SignedByte = "byte"
show CharByte = "char"
show DoubleType = "double"
show FloatType = "float"
show IntType = "int"
show LongInt = "long"
show ShortInt = "short"
show BoolType = "bool"
show (ObjectType s) = "Object " ++ s
show (Array Nothing t) = show t ++ "[]"
show (Array (Just n) t) = show t ++ "[" ++ show n ++ "]"
-- | Class field signature
type FieldSignature = FieldType
-- | Try to read integer value from decimal representation
getInt :: Get (Maybe Int)
getInt = do
s <- getDigits
if null s
then return Nothing
else return $ Just (read s)
where
getDigits :: Get [Char]
getDigits = do
c <- lookAhead getChar8
if isDigit c
then do
skip 1
next <- getDigits
return (c: next)
else return []
putString :: String -> Put
putString str = forM_ str put
instance Binary FieldType where
put SignedByte = put 'B'
put CharByte = put 'C'
put DoubleType = put 'D'
put FloatType = put 'F'
put IntType = put 'I'
put LongInt = put 'J'
put ShortInt = put 'S'
put BoolType = put 'Z'
put (ObjectType name) = put 'L' >> putString name >> put ';'
put (Array Nothing sig) = put '[' >> put sig
put (Array (Just n) sig) = put '[' >> put (show n) >> put sig
get = do
b <- getChar8
case b of
'B' -> return SignedByte
'C' -> return CharByte
'D' -> return DoubleType
'F' -> return FloatType
'I' -> return IntType
'J' -> return LongInt
'S' -> return ShortInt
'Z' -> return BoolType
'L' -> do
name <- getToSemicolon
return (ObjectType name)
'[' -> do
mbSize <- getInt
sig <- get
return (Array mbSize sig)
_ -> fail $ "Unknown signature opening symbol: " ++ [b]
-- | Read string up to `;'
getToSemicolon :: Get String
getToSemicolon = do
x <- get
if x == ';'
then return []
else do
next <- getToSemicolon
return (x: next)
-- | Return value signature
data ReturnSignature =
Returns FieldType
| ReturnsVoid
deriving (Eq)
instance Show ReturnSignature where
show (Returns t) = show t
show ReturnsVoid = "Void"
instance Binary ReturnSignature where
put (Returns sig) = put sig
put ReturnsVoid = put 'V'
get = do
x <- lookAhead getChar8
case x of
'V' -> skip 1 >> return ReturnsVoid
_ -> Returns <$> get
-- | Method argument signature
type ArgumentSignature = FieldType
-- | Class method argument signature
data MethodSignature =
MethodSignature [ArgumentSignature] ReturnSignature
deriving (Eq)
instance Show MethodSignature where
show (MethodSignature args ret) = "(" ++ intercalate ", " (map show args) ++ ") returns " ++ show ret
instance Binary MethodSignature where
put (MethodSignature args ret) = do
put '('
forM_ args put
put ')'
put ret
get = do
x <- getChar8
when (x /= '(') $
fail "Cannot parse method signature: no starting `(' !"
args <- getArgs
y <- getChar8
when (y /= ')') $
fail "Internal error: method signature without `)' !?"
ret <- get
return (MethodSignature args ret)
-- | Read arguments signatures (up to `)')
getArgs :: Get [ArgumentSignature]
getArgs = whileJust getArg
where
getArg :: Get (Maybe ArgumentSignature)
getArg = do
x <- lookAhead getChar8
if x == ')'
then return Nothing
else Just <$> get
whileJust :: (Monad m) => m (Maybe a) -> m [a]
whileJust m = do
r <- m
case r of
Just x -> do
next <- whileJust m
return (x: next)
Nothing -> return []
-- | Constant pool item format
data CpInfo =
CONSTANT_Class {nameIndex :: Word16} -- ^ 7
| CONSTANT_Fieldref {classIndex :: Word16, nameAndTypeIndex :: Word16} -- ^ 9
| CONSTANT_Methodref {classIndex :: Word16, nameAndTypeIndex :: Word16} -- ^ 10
| CONSTANT_InterfaceMethodref {classIndex :: Word16, nameAndTypeIndex :: Word16}-- ^ 11
| CONSTANT_String {stringIndex :: Word16} -- ^ 8
| CONSTANT_Integer {fourBytes :: Word32} -- ^ 3
| CONSTANT_Float Float -- ^ 4
| CONSTANT_Long Word64 -- ^ 5
| CONSTANT_Double Double -- ^ 6
| CONSTANT_NameAndType {nameIndex :: Word16, signatureIndex :: Word16} -- ^ 12
| CONSTANT_Utf8 {stringLength :: Word16, stringBytes :: B.ByteString} -- ^ 1
| CONSTANT_Unicode {stringLength :: Word16, stringBytes :: B.ByteString} -- ^ 2
deriving (Eq, Show)
instance Binary CpInfo where
put (CONSTANT_Class i) = putWord8 7 >> put i
put (CONSTANT_Fieldref i j) = putWord8 9 >> put i >> put j
put (CONSTANT_Methodref i j) = putWord8 10 >> put i >> put j
put (CONSTANT_InterfaceMethodref i j) = putWord8 11 >> put i >> put j
put (CONSTANT_String i) = putWord8 8 >> put i
put (CONSTANT_Integer x) = putWord8 3 >> put x
put (CONSTANT_Float x) = putWord8 4 >> putFloat32be x
put (CONSTANT_Long x) = putWord8 5 >> put x
put (CONSTANT_Double x) = putWord8 6 >> putFloat64be x
put (CONSTANT_NameAndType i j) = putWord8 12 >> put i >> put j
put (CONSTANT_Utf8 l bs) = putWord8 1 >> put l >> putLazyByteString bs
put (CONSTANT_Unicode l bs) = putWord8 2 >> put l >> putLazyByteString bs
get = do
!offset <- bytesRead
tag <- getWord8
case tag of
1 -> do
l <- get
bs <- getLazyByteString (fromIntegral l)
return $ CONSTANT_Utf8 l bs
2 -> do
l <- get
bs <- getLazyByteString (fromIntegral l)
return $ CONSTANT_Unicode l bs
3 -> CONSTANT_Integer <$> get
4 -> CONSTANT_Float <$> getFloat32be
5 -> CONSTANT_Long <$> get
6 -> CONSTANT_Double <$> getFloat64be
7 -> CONSTANT_Class <$> get
8 -> CONSTANT_String <$> get
9 -> CONSTANT_Fieldref <$> get <*> get
10 -> CONSTANT_Methodref <$> get <*> get
11 -> CONSTANT_InterfaceMethodref <$> get <*> get
12 -> CONSTANT_NameAndType <$> get <*> get
_ -> fail $ "Unknown constants pool entry tag: " ++ show tag
-- | Class field format
data FieldInfo = FieldInfo {
fieldAccessFlags :: Word16,
fieldNameIndex :: Word16,
fieldSignatureIndex :: Word16,
fieldAttributesCount :: Word16,
fieldAttributes :: [AttributeInfo] }
deriving (Eq, Show)
instance Binary FieldInfo where
put (FieldInfo {..}) = do
put fieldAccessFlags
put fieldNameIndex
put fieldSignatureIndex
put fieldAttributesCount
forM_ fieldAttributes put
get = do
af <- get
ni <- get
si <- get
n <- get
as <- replicateM (fromIntegral n) get
return $ FieldInfo af ni si n as
-- | Class method format
data MethodInfo = MethodInfo {
methodAccessFlags :: Word16,
methodNameIndex :: Word16,
methodSignatureIndex :: Word16,
methodAttributesCount :: Word16,
methodAttributes :: [AttributeInfo] }
deriving (Eq, Show)
instance Binary MethodInfo where
put (MethodInfo {..}) = do
put methodAccessFlags
put methodNameIndex
put methodSignatureIndex
put methodAttributesCount
forM_ methodAttributes put
get = do
offset <- bytesRead
af <- get
ni <- get
si <- get
n <- get
as <- replicateM (fromIntegral n) get
return $ MethodInfo af ni si n as
-- | Any (class/ field/ method/ ...) attribute format.
-- Some formats specify special formats for @attributeValue@.
data AttributeInfo = AttributeInfo {
attributeName :: Word16,
attributeLength :: Word32,
attributeValue :: B.ByteString }
deriving (Eq, Show)
instance Binary AttributeInfo where
put (AttributeInfo {..}) = do
put attributeName
putWord32be attributeLength
putLazyByteString attributeValue
get = do
offset <- bytesRead
name <- get
len <- getWord32be
value <- getLazyByteString (fromIntegral len)
return $ AttributeInfo name len value