jvm-binary-0.7.0: src/Language/JVM/Constant.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE StrictData #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-|
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.
-}
module Language.JVM.Constant
( Constant (..)
, constantSize
, typeToStr
, Referenceable (..)
, JValue (..)
, VInteger
, VLong
, VDouble
, VFloat
, VString
-- * Special constants
, ClassName (..)
, InClass (..)
, AbsMethodId
, AbsFieldId
, AbsInterfaceMethodId (..)
, AbsVariableMethodId (..)
, MethodId (..)
, FieldId (..)
, NameAndType (..)
, MethodDescriptor
, FieldDescriptor
, MethodHandle (..)
, MethodHandleField (..)
, MethodHandleMethod (..)
, MethodHandleInterface (..)
, MethodHandleFieldKind (..)
, InvokeDynamic (..)
-- * re-exports
, High
, Low
) where
import Control.DeepSeq (NFData)
import Control.Monad.Reader
import Data.Binary
import Data.String
import Data.Binary.IEEE754
import qualified Data.ByteString as BS
import Data.Int
import qualified Data.Text as Text
import qualified Data.Text.Encoding.Error as TE
import GHC.Generics (Generic)
import Numeric (showHex)
import Prelude hiding (fail, lookup)
import Language.JVM.Stage
import Language.JVM.TH
import Language.JVM.Type
import Language.JVM.Utils
-- | A constant is a multi word item in the 'ConstantPool'. Each of
-- the constructors are pretty much self-explanatory from the types.
data Constant r
= CString !SizedByteString16
| CInteger !Int32
| CFloat !Float
| CLong !Int64
| CDouble !Double
| CClassRef !(Ref Text.Text r)
| CStringRef !(Ref BS.ByteString r)
| CFieldRef !(InClass FieldId r)
| CMethodRef !(InClass MethodId r)
| CInterfaceMethodRef !(InClass MethodId r)
| CNameAndType !(Ref Text.Text r) !(Ref Text.Text r)
| CMethodHandle !(MethodHandle r)
| CMethodType !(Ref MethodDescriptor r)
| CInvokeDynamic !(InvokeDynamic r)
--deriving (Show, Eq, Generic, NFData)
-- | Anything pointing inside a class
data InClass a r = InClass
{ inClassName :: !(Ref ClassName r)
, inClassId :: !(Ref a r)
}
deriving instance Show a => Show (InClass a High)
deriving instance Eq a => Eq (InClass a High)
deriving instance Ord a => Ord (InClass a High)
deriving instance Generic a => Generic (InClass a High)
deriving instance (Generic a, NFData a) => NFData (InClass a High)
deriving instance Eq (InClass a Low)
deriving instance Ord (InClass a Low)
deriving instance Show (InClass a Low)
deriving instance NFData (InClass a Low)
deriving instance Generic (InClass a Low)
deriving instance Binary (InClass a Low)
-- | A method id in a class.
type AbsMethodId = InClass MethodId
-- | A field id in a class
type AbsFieldId = InClass FieldId
-- | An method which is from an interface
newtype AbsInterfaceMethodId r = AbsInterfaceMethodId
{ interfaceMethodId :: InClass MethodId r
}
-- | An method which can be from an interface
data AbsVariableMethodId r = AbsVariableMethodId
{ variableIsInterface :: !Bool
, variableMethodId :: !(InClass MethodId r)
}
newtype MethodId = MethodId (NameAndType MethodDescriptor)
deriving (Eq, Show, NFData, Ord, Generic)
newtype FieldId = FieldId (NameAndType FieldDescriptor)
deriving (Eq, Show, NFData, Ord, Generic)
instance IsString FieldId where
fromString = FieldId . fromString
instance IsString MethodId where
fromString = MethodId . fromString
-- | The union type over the different method handles.
data MethodHandle r
= MHField !(MethodHandleField r)
| MHMethod !(MethodHandleMethod r)
| MHInterface !(MethodHandleInterface r)
data MethodHandleField r = MethodHandleField
{ methodHandleFieldKind :: !MethodHandleFieldKind
, methodHandleFieldRef :: !(DeepRef AbsFieldId r)
}
data MethodHandleFieldKind
= MHGetField
| MHGetStatic
| MHPutField
| MHPutStatic
deriving (Eq, Show, NFData, Generic, Ord)
data MethodHandleMethod r
= MHInvokeVirtual !(DeepRef AbsMethodId r)
| MHInvokeStatic !(DeepRef AbsVariableMethodId r)
-- ^ Since version 52.0
| MHInvokeSpecial !(DeepRef AbsVariableMethodId r)
-- ^ Since version 52.0
| MHNewInvokeSpecial !(DeepRef AbsMethodId r)
data MethodHandleInterface r = MethodHandleInterface
{ methodHandleInterfaceRef :: !(DeepRef AbsInterfaceMethodId r)
}
data InvokeDynamic r = InvokeDynamic
{ invokeDynamicAttrIndex :: !Word16
, invokeDynamicMethod :: !(Ref MethodId r)
}
-- | Hack that returns the name of a constant.
typeToStr :: Constant r -> String
typeToStr c =
case c of
CString _ -> "CString"
CInteger _ -> "CInteger"
CFloat _ -> "CFloat"
CLong _ -> "CLong"
CDouble _ -> "CDouble"
CClassRef _ -> "CClassRef"
CStringRef _ -> "CStringRef"
CFieldRef _ -> "CFieldRef"
CMethodRef _ -> "CMethodRef"
CInterfaceMethodRef _ -> "CInterfaceMethodRef"
CNameAndType _ _ -> "CNameAndType"
CMethodHandle _ -> "CMethodHandle"
CMethodType _ -> "CMethodType"
CInvokeDynamic _ -> "CInvokeDynamic"
instance Binary (Constant Low) where
get = do
ident <- getWord8
case ident of
1 -> CString <$> get
3 -> CInteger <$> get
4 -> CFloat <$> getFloat32be
5 -> CLong <$> get
6 -> CDouble <$> getFloat64be
7 -> CClassRef <$> get
8 -> CStringRef <$> get
9 -> CFieldRef <$> get
10 -> CMethodRef <$> get
11 -> CInterfaceMethodRef <$> get
12 -> CNameAndType <$> get <*> get
15 -> CMethodHandle <$> get
16 -> CMethodType <$> get
18 -> CInvokeDynamic <$> get
_ -> fail $ "Unknown identifier " ++ show ident
put x =
case x of
CString bs -> do putWord8 1; put bs
CInteger i -> do putWord8 3; put i
CFloat i -> do putWord8 4; putFloat32be i
CLong i -> do putWord8 5; put i
CDouble i -> do putWord8 6; putFloat64be i
CClassRef i -> do putWord8 7; put i
CStringRef i -> do putWord8 8; put i
CFieldRef i -> do putWord8 9; put i
CMethodRef i -> do putWord8 10; put i
CInterfaceMethodRef i -> do putWord8 11; put i
CNameAndType i j -> do putWord8 12; put i; put j
CMethodHandle h -> do putWord8 15; put h
CMethodType i -> do putWord8 16; put i;
CInvokeDynamic i -> do putWord8 18; put i
instance Binary (MethodHandle Low) where
get = do
w <- getWord8
case w of
1 -> MHField . MethodHandleField MHGetField <$> get
2 -> MHField . MethodHandleField MHGetStatic <$> get
3 -> MHField . MethodHandleField MHPutField <$> get
4 -> MHField . MethodHandleField MHPutStatic <$> get
5 -> MHMethod . MHInvokeVirtual <$> get
6 -> MHMethod . MHInvokeStatic <$> get
7 -> MHMethod . MHInvokeSpecial<$> get
8 -> MHMethod . MHNewInvokeSpecial <$> get
9 -> MHInterface . MethodHandleInterface <$> get
_ -> fail $ "Unknown method handle kind 'x" ++ showHex w "'"
put x = case x of
MHField h -> do
putWord8 $ case methodHandleFieldKind h of
MHGetField -> 1
MHGetStatic -> 2
MHPutField -> 3
MHPutStatic -> 4
put $ methodHandleFieldRef h
MHMethod h -> do
case h of
MHInvokeVirtual m -> putWord8 5 >> put m
MHInvokeStatic m -> putWord8 6 >> put m
MHInvokeSpecial m -> putWord8 7 >> put m
MHNewInvokeSpecial m -> putWord8 8 >> put m
MHInterface h -> do
putWord8 9
put $ methodHandleInterfaceRef h
-- | 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 r -> Index
constantSize x =
case x of
CDouble _ -> 2
CLong _ -> 2
_ -> 1
-- | 'Referenceable' is something that can exist in the constant pool.
class Referenceable a where
fromConst
:: (Monad m)
=> (forall a'. String -> m a')
-> Constant High
-> m a
toConst
:: (Monad m)
=> a
-> m (Constant High)
instance Referenceable (Constant High) where
fromConst _ = return
toConst = return
instance TypeParse a => Referenceable (NameAndType a) where
fromConst err (CNameAndType rn txt) = do
md <- either err return $ typeFromText txt
return $ NameAndType rn md
fromConst e c = expected "CNameAndType" e c
toConst (NameAndType rn md) =
return $ CNameAndType rn (typeToText md)
-- TODO: Find good encoding of string.
instance Referenceable Text.Text where
fromConst err c =
case c of
CString str ->
case sizedByteStringToText str of
Left (TE.DecodeError msg _) ->
err $ badEncoding msg (unSizedByteString str)
Left _ -> error "This is deprecated in the api"
Right txt -> return txt
a -> err $ wrongType "String" a
toConst =
return . CString . sizedByteStringFromText
instance Referenceable BS.ByteString where
fromConst err c =
case c of
CString str -> return $ unSizedByteString str
a -> err $ wrongType "String" a
toConst =
return . CString . SizedByteString
instance Referenceable ClassName where
fromConst _ (CClassRef r) =
return . ClassName $ r
fromConst err a =
err $ wrongType "ClassRef" a
toConst (ClassName txt) = do
return . CClassRef $ txt
instance Referenceable JRefType where
fromConst err (CClassRef r) =
either err return $ parseOnly parseFlatJRefType r
fromConst err a =
err $ wrongType "ClassRef" a
toConst =
return . CClassRef . jRefTypeToFlatText
instance Referenceable ReturnDescriptor where
fromConst err =
fromConst err >=> either err return . typeFromText
toConst = toConst . typeToText
instance Referenceable MethodDescriptor where
fromConst err =
fromConst err >=> either err pure . typeFromText
toConst = toConst . typeToText
instance Referenceable FieldDescriptor where
fromConst err =
fromConst err >=> either err pure . typeFromText
toConst = toConst . typeToText
-- instance TypeParse f => Referenceable (NameAndType f) where
-- fromConst err =
-- fromConst err >=> either err pure . fromText
-- toConst = toConst . typeToText
instance Referenceable MethodId where
fromConst err x = MethodId <$> fromConst err x
toConst (MethodId s) = toConst s
instance Referenceable FieldId where
fromConst err x = FieldId <$> fromConst err x
toConst (FieldId s) = toConst s
instance Referenceable (InClass FieldId High) where
fromConst _ (CFieldRef s) = do
return $ s
fromConst err c = expected "CFieldRef" err c
toConst s =
return $ CFieldRef s
instance Referenceable (InClass MethodId High) where
fromConst _ (CMethodRef s) = do
return $ s
fromConst err c = expected "CMethodRef" err c
toConst s =
return $ CMethodRef s
instance Referenceable (AbsVariableMethodId High) where
fromConst _ (CMethodRef s) = do
return $ AbsVariableMethodId False s
fromConst _ (CInterfaceMethodRef s) = do
return $ AbsVariableMethodId True s
fromConst err c = expected "CMethodRef or CInterfaceMethodRef" err c
toConst (AbsVariableMethodId t s)
| t =
return $ CInterfaceMethodRef s
| otherwise =
return $ CMethodRef s
instance Referenceable (InvokeDynamic High) where
fromConst _ (CInvokeDynamic c) = return c
fromConst err c = expected "CInvokeDynamic" err c
toConst s =
return $ CInvokeDynamic s
instance Referenceable (MethodHandle High) where
fromConst _ (CMethodHandle c) = return c
fromConst err c = expected "CMethodHandle" err c
toConst s =
return $ CMethodHandle s
instance Referenceable (AbsInterfaceMethodId High) where
fromConst _ (CInterfaceMethodRef s) =
return . AbsInterfaceMethodId $ s
fromConst err c = expected "CInterfaceMethodRef" err c
toConst (AbsInterfaceMethodId s) =
return $ CInterfaceMethodRef s
expected :: String -> (String -> a) -> (Constant r) -> a
expected name err c =
err $ wrongType name c
wrongType :: String -> Constant r -> String
wrongType n c =
"Expected '" ++ n ++ "', but found '" ++ typeToStr c ++ "'."
badEncoding :: String -> BS.ByteString -> String
badEncoding str bs =
"Could not encode '" ++ str ++ "': " ++ show bs
-- $(deriveBaseWithBinary ''MethodId)
-- $(deriveBaseWithBinary ''FieldId)
$(deriveBase ''Constant)
$(deriveBase ''MethodHandle)
$(deriveBase ''MethodHandleField)
$(deriveBase ''MethodHandleMethod)
$(deriveBase ''MethodHandleInterface)
$(deriveBaseWithBinary ''InvokeDynamic)
-- $(deriveBaseWithBinary ''AbsMethodId)
-- $(deriveBaseWithBinary ''AbsFieldId)
$(deriveBaseWithBinary ''AbsInterfaceMethodId)
$(deriveBaseWithBinary ''AbsVariableMethodId)
type VInteger = Int32
type VLong = Int64
type VFloat = Float
type VDouble = Double
type VString = BS.ByteString
instance Referenceable VInteger where
fromConst err = \case
CInteger i -> return i
x -> expected "Integer" err x
toConst = return . CInteger
instance Referenceable VLong where
fromConst err = \case
CLong i -> return i
x -> expected "Long" err x
toConst = return . CLong
instance Referenceable VFloat where
fromConst err = \case
CFloat i -> return i
x -> expected "Float" err x
toConst = return . CFloat
instance Referenceable VDouble where
fromConst err = \case
CDouble i -> return i
x -> expected "Double" err x
toConst = return . CDouble
-- instance Referenceable VString where
-- fromConst err = \case
-- CStringRef i -> return i
-- x -> expected "StringRef" err x
-- toConst = return . CStringRef
-- | A constant pool value in java
data JValue
= VInteger VInteger
| VLong VLong
| VFloat VFloat
| VDouble VDouble
| VString VString
| VClass ClassName
| VMethodType MethodDescriptor
| VMethodHandle (MethodHandle High)
deriving (Show, Eq, Generic, NFData)
instance Referenceable JValue where
fromConst err = \case
CStringRef s -> return $ VString s
CInteger i -> return $ VInteger i
CFloat f -> return $ VFloat f
CLong l -> return $ VLong l
CDouble d -> return $ VDouble d
CClassRef r -> return $ VClass (ClassName r)
CMethodHandle m -> return $ VMethodHandle m
CMethodType t -> return $ VMethodType t
x -> expected "Value" err x
{-# INLINE fromConst #-}
toConst = return . \case
VString s -> CStringRef s
VInteger i -> CInteger i
VFloat f -> CFloat f
VLong l -> CLong l
VDouble d -> CDouble d
VClass (ClassName r) -> CClassRef r
VMethodHandle m -> CMethodHandle m
VMethodType t -> CMethodType t
{-# INLINE toConst #-}