jvm-binary-0.0.1: src/Language/JVM/Constant.hs
{-|
Module : Language.JVM.Constant
Copyright : (c) Christian Gram Kalhauge, 2017
License : MIT
Maintainer : kalhuage@cs.ucla.edu
This module contains the 'Constant' type and the 'ConstantPool'. These
are essential for accessing data in the class-file.
-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE RankNTypes #-}
module Language.JVM.Constant
(
-- * Constant
Constant (..)
, constantSize
, ConstantRef (..)
-- * Constant Pool
-- $ConstantPool
, ConstantPool (..)
, lookupConstant
, lookupText
, lookupClassName
, toListOfConstants
) where
import GHC.Generics (Generic)
import Prelude hiding (fail, lookup)
import Control.Monad (forM_)
import Control.Monad.Fail (fail)
import Data.Binary
import Data.Binary.Get
import Data.Binary.Put
import qualified Data.IntMap.Strict as IM
import Language.JVM.Utils
import qualified Data.Text as Text
import qualified Data.Text.Encoding as TE
-- | A constant is referenced by the ConstantRef data type. It is
-- just a 16 bit word, but wrapped for type safety.
newtype ConstantRef =
ConstantRef Word16
deriving (Eq, Show, Generic)
instance Binary ConstantRef where
-- | A constant is a multi word item in the 'ConstantPool'. Each of
-- the constructors are pretty much self expiatory from the types.
data Constant
= String !SizedByteString16
| Integer !Word32
| Float !Word32
| Long !Word64
| Double !Word64
| ClassRef !ConstantRef
| StringRef !ConstantRef
| FieldRef !ConstantRef !ConstantRef
| MethodRef !ConstantRef !ConstantRef
| InterfaceMethodRef !ConstantRef !ConstantRef
| NameAndType !ConstantRef !ConstantRef
| MethodHandle !Word8 !ConstantRef
| MethodType !ConstantRef
| InvokeDynamic !ConstantRef !ConstantRef
deriving (Show, Eq)
instance Binary Constant where
get = do
ident <- getWord8
case ident of
1 -> String <$> get
3 -> Integer <$> get
4 -> Float <$> get
5 -> Long <$> get
6 -> Double <$> get
7 -> ClassRef <$> get
8 -> StringRef <$> get
9 -> FieldRef <$> get <*> get
10 -> MethodRef <$> get <*> get
11 -> InterfaceMethodRef <$> get <*> get
12 -> NameAndType <$> get <*> get
15 -> MethodHandle <$> get <*> get
16 -> MethodType <$> get
18 -> InvokeDynamic <$> get <*> get
_ -> fail $ "Unkown identifier " ++ show ident
put x =
case x of
String bs -> do putWord8 1; put bs
Integer i -> do putWord8 3; put i
Float i -> do putWord8 4; put i
Long i -> do putWord8 5; put i
Double i -> do putWord8 6; put i
ClassRef i -> do putWord8 7; put i
StringRef i -> do putWord8 8; put i
FieldRef i j -> do putWord8 9; put i; put j
MethodRef i j -> do putWord8 10; put i; put j
InterfaceMethodRef i j -> do putWord8 11; put i; put j
NameAndType i j -> do putWord8 12; put i; put j
MethodHandle i j -> do putWord8 15; put i; put j
MethodType i -> do putWord8 16; put i;
InvokeDynamic i j -> do putWord8 18; put i; put j
-- | Some of the 'Constant's take up more space in the constant pool than other.
-- Notice that 'Language.JVM.Constant.String' and 'MethodType' is not of size
-- 32, but is still awarded value 1. This is due to an
-- [inconsistency](http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4.5)
-- in JVM.
constantSize :: Constant -> Int
constantSize x =
case x of
Double _ -> 2
Long _ -> 2
_ -> 1
-- $ConstantPool
--
-- The 'ConstantPool' contains all the constants, and is accessible using the
-- Lookup methods.
-- | A ConstantPool is just an 'IntMap'. A 'IntMap' is used, because constants are
-- accessed after their byte-offset. 'constantSize'
newtype ConstantPool = ConstantPool
{ unConstantPool :: IM.IntMap Constant
} deriving (Show, Eq)
-- | Return a list of reference-constant pairs.
toListOfConstants :: ConstantPool -> [(ConstantRef, Constant)]
toListOfConstants =
map (\(a,b) -> (ConstantRef . fromIntegral $ a, b)) . IM.toList . unConstantPool
instance Binary ConstantPool where
get = do
len <- fromIntegral <$> getInt16be
list <- go len 1
return . ConstantPool $ IM.fromList list
where
go len n | len > n = do
constant <- get
rest <- go len (n + constantSize constant)
return $ (n, constant) : rest
go _ _ = return []
put (ConstantPool p)= do
case IM.maxViewWithKey p of
Just ((key, e), _) -> do
putInt16be (fromIntegral (key + constantSize e))
forM_ (IM.toAscList p) (put . snd)
Nothing -> do
putInt16be 0
-- | Lookup a 'Constant' in the 'ConstantPool'.
lookupConstant :: ConstantRef -> ConstantPool -> Maybe Constant
lookupConstant (ConstantRef ref) (ConstantPool cp) =
IM.lookup (fromIntegral ref) cp
-- | Lookup a 'Text.Text' in the 'ConstantPool', returns 'Nothing' if the
-- reference does not point to something in the ConstantPool, if it points to
-- something not a 'Language.JVM.Constant.String' Constant, or if it is
-- impossible to decode the 'ByteString' as Utf8.
lookupText :: ConstantRef -> ConstantPool -> Maybe Text.Text
lookupText ref cp = do
String str <- lookupConstant ref cp
case TE.decodeUtf8' . unSizedByteString $ str of
Left _ -> Nothing
Right txt -> Just txt
-- | Lookup a class name in the 'ConstantPool', returns 'Nothing' otherwise.
lookupClassName :: ConstantRef -> ConstantPool -> Maybe Text.Text
lookupClassName ref cp = do
ClassRef r <- lookupConstant ref cp
lookupText r cp