{{ message }}
Repository navigation
Expand file tree
/
Copy pathClassFile.hs
More file actions
422 lines (353 loc) · 15.2 KB
/
Copy pathClassFile.hs
File metadata and controls
422 lines (353 loc) · 15.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
{-# LANGUAGE DoRec #-}
module Language.Java.ClassFile (
-- * Classfile parsing
ClassImportResult(..),
InnerType(..),
getClass,
-- parseClasses,
-- parseJar,
-- * Magic identifiers
nameConstructor, nameClassConstructor
) where
import Language.Java.Syntax
import Data.Binary.Get
import Data.Binary.IEEE754
import Data.ByteString.UTF8 (toString)
import Data.Maybe
import Control.Monad
import Data.Word
import Control.Applicative ((<$>), (<$))
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import Data.Flags
import Control.Monad.Reader
import Text.ParserCombinators.Parsec
import Data.List
import Control.DeepSeq
import Data.Monoid
parseFull :: Parser a -> Parser a
-- language-java depends on Parsec 2, which is not Applicative...
-- parseFull p = p <* eof
parseFull p =
do x <- p
eof
return x
parseType :: Parser Type
parseType =
(PrimType ByteT <$ char 'B') <|>
(PrimType CharT <$ char 'C') <|>
(PrimType DoubleT <$ char 'D') <|>
(PrimType FloatT <$ char 'F') <|>
(PrimType IntT <$ char 'I') <|>
(PrimType LongT <$ char 'J') <|>
(PrimType ShortT <$ char 'S') <|>
(PrimType BooleanT <$ char 'Z') <|>
(RefType <$> ArrayType <$> (char '[' >> parseType)) <|>
(RefType <$> ClassRefType <$> (char 'L' >> parseClassTypeEnd))
readType :: String -> Type
readType s = case parse (parseFull parseType) "" s of
Right x -> x
Left e -> error $ show e
parseRefType :: Parser RefType
parseRefType =
ArrayType <$> (char '[' >> parseType) <|>
ClassRefType <$> parseClassType
readRefType :: String -> RefType
readRefType s =
case parse (parseFull parseRefType) "" s of
Right x -> x
Left e -> error $ unwords [show s, show e]
parseClassType :: Parser ClassType
parseClassType =
do parts <- (many1 $ alphaNum <|> oneOf "-_") `sepBy` (char '/' <|> char '$')
let ids = map (\ part -> (Ident part, [])) parts
return $ ClassType ids
parseClassTypeEnd =
do x <- parseClassType
_ <- char ';'
return x
parseMethodType :: Parser (Maybe Type, [Type])
parseMethodType =
do args <- between (char '(') (char ')') (many parseType)
ret <- (Nothing <$ char 'V') <|> (Just <$> parseType)
return (ret, args)
readMethodType s = case parse (parseFull parseMethodType) "" s of
Right x -> x
Left e -> error $ show e
type ConstIdx = IntMap.Key
getConstIdx = fromIntegral <$> getWord16be
data ConstantValue = ValueInteger Word32
| ValueFloat Float
| ValueLong Word64
| ValueDouble Double
| ValueString String
deriving Show
data Constant = ConstantClass { constRefType :: RefType }
| ConstantValue ConstantValue
| ConstantString String
deriving Show
type ConstTable = IntMap Constant
type ClassImporter a = ReaderT ConstTable Get a
getMany f =
do count <- lift getWord16be
forM [1..count] $ const f
getConstant :: ConstTable -> Get (ConstIdx, Maybe Constant)
getConstant constTable =
do tag <- getWord8
case tag of
1 -> noSkip <$> Just <$> ConstantString <$> getString
3 -> noSkip <$> Just <$> ConstantValue <$> ValueInteger <$> getWord32be
4 -> noSkip <$> Just <$> ConstantValue <$> ValueFloat <$> getFloat32be
5 -> skip <$> Just <$> ConstantValue <$> ValueLong <$> getWord64be
6 -> skip <$> Just <$> ConstantValue <$> ValueDouble <$> getFloat64be
7 -> noSkip <$> Just <$> ConstantClass <$> readRefType <$> getStringRef
8 -> noSkip <$> Just <$> ConstantValue <$> ValueString <$> getStringRef
9 -> noSkip <$> (getConstIdx >> getConstIdx >> return Nothing)
10 -> noSkip <$> (getConstIdx >> getConstIdx >> return Nothing)
11 -> noSkip <$> (getConstIdx >> getConstIdx >> return Nothing)
12 -> noSkip <$> (getConstIdx >> getConstIdx >> return Nothing)
tag -> fail $ unwords ["Unknown tag type:", show tag]
where
getString = toString <$> (getByteString =<< (fromInteger . fromIntegral <$> getWord16be))
-- lookupString idx = case IntMap.lookup idx constTable of
-- Just (ConstantString s) -> s
-- getStringRef = lookupString <$> getConstIdx
getStringRef = do idx <- getConstIdx
let Just (ConstantString s) = IntMap.lookup idx constTable
return s
noSkip x = (1, x)
skip x = (2, x)
getConstants :: Get ConstTable
getConstants =
do count <- fromIntegral <$> getWord16be
rec
-- According to the Java .class spec, count is, for some reason, one larger than the actual number of constants
consts <- IntMap.fromAscList <$> mapMaybe filter <$> (fromToM 1 (count - 1) $ \i ->
do (step, const) <- getConstant consts
return (step, (i, const)))
return consts
where filter (_, Nothing) = Nothing
filter (i, Just c) = Just (i, c)
fromToM from to f | from > to = return []
| otherwise =
do (step, x) <- f from
liftM (x:) $ fromToM (from + step) to f
classObject :: ClassType
classObject = ClassType [(Ident "java", []), (Ident "lang", []), (Ident "Object", [])]
-- |Constructors are encoded in .class files as member functions of the name \"@\<init>@\".
nameConstructor :: String
nameConstructor = "<init>"
-- |Class constructors are encoded in .class files as static member functions of the name \"@\<clinit>@\".
nameClassConstructor :: String
nameClassConstructor = "<clinit>"
constClassType con = let ClassRefType cls = constRefType con in cls
getClassTypeMaybe :: ClassImporter (Maybe ClassType)
getClassTypeMaybe = fmap constClassType <$> getConstantRefMaybe
getClassType :: ClassImporter ClassType
getClassType = constClassType <$> fromMaybe (error "getClassType") <$> getConstantRefMaybe
getHeader =
do signature <- replicateM 4 getWord8
unless (signature == [0xCA, 0xFE, 0xBA, 0xBE]) $
error "Not a class file"
minor <- getWord16be
major <- getWord16be
return (major, minor)
getConstantRefMaybe :: ClassImporter (Maybe Constant)
getConstantRefMaybe =
do idx <- lift getConstIdx
if idx == 0 then return Nothing else asks (IntMap.lookup idx)
getConstantRef :: ClassImporter Constant
getConstantRef = fromMaybe (error "getConstantRef") <$> getConstantRefMaybe
getStringRefMaybe ::ClassImporter (Maybe String)
getStringRefMaybe =
do const <- getConstantRefMaybe
return $ case const of
Just (ConstantString s) -> Just s
_ -> Nothing
getStringRef :: ClassImporter String
getStringRef = fromMaybe (fail "getStringRef") <$> getStringRefMaybe
data InnerClass = InnerClass { innerInner :: ClassType,
innerOuter :: Maybe ClassType,
innerName :: Maybe String,
innerFlags :: Word16 }
deriving Show
data Attribute = AttrConstantValue ConstantValue
| AttrExceptions [ClassType]
| AttrSynthetic
| AttrInnerClasses [InnerClass]
| AttrSourceFile String
deriving Show
getInnerClass :: ClassImporter InnerClass
getInnerClass =
do inner <- getClassType
outer <- fmap constClassType <$> getConstantRefMaybe
name <- getStringRefMaybe
flags <- lift getWord16be
return $ InnerClass inner outer name flags
getAttribute :: ClassImporter (Maybe Attribute)
getAttribute =
do name <- getStringRef
len <- lift getWord32be
case name of
"Synthetic" ->
return $ Just $ AttrSynthetic
"ConstantValue" ->
do const <- getConstantRef
return $ Just $ AttrConstantValue $
case const of
ConstantValue val -> val
"Exceptions" ->
Just <$> AttrExceptions <$> getMany getClassType
"InnerClasses" ->
Just <$> AttrInnerClasses <$> getMany getInnerClass
"SourceFile" ->
Just <$> AttrSourceFile <$> getStringRef
_ ->
do lift $ skip (fromIntegral len)
return Nothing
getAttributes = catMaybes <$> getMany getAttribute
getField :: ClassImporter (Maybe MemberDecl)
getField =
do modifiers <- getModifiers
name <- getStringRef
ty <- readType <$> getStringRef
attributes <- getAttributes
let init =
case find isAttrConstantValue attributes of
Nothing -> Nothing
Just (AttrConstantValue val) -> Just $ InitExp $ Lit $ case val of
ValueInteger x -> Int $ fromIntegral x
ValueLong x -> Word $ fromIntegral x
ValueString s -> String s
ValueFloat x -> Float $ realToFrac x
ValueDouble x -> Double x
return $ unlessSynthetic attributes $
FieldDecl modifiers ty [VarDecl (VarId $ Ident name) init]
where isAttrConstantValue (AttrConstantValue _) = True
isAttrConstantValue _ = False
getMethod :: Ident -> ClassImporter (Maybe MemberDecl)
getMethod cls =
do modifiers <- getModifiers
name <- getStringRef
let isConstructor = name `elem` [nameConstructor, nameClassConstructor]
(ret, args) <- readMethodType <$> getStringRef
let formals = zipWith (\i ty -> FormalParam [] ty False (VarId $ Ident $ 'p':show i)) [(1::Integer)..] args
attributes <- getAttributes
let getAttrExceptions (AttrExceptions tys) = Just tys
getAttrExceptions _ = Nothing
exceptions = case mapFirst getAttrExceptions attributes of
Nothing -> []
Just tys -> map ClassRefType tys
return $ unlessSynthetic attributes $
if isConstructor
then ConstructorDecl modifiers [] cls formals exceptions (ConstructorBody Nothing [])
else MethodDecl modifiers [] ret (Ident name) formals exceptions (MethodBody Nothing)
unlessSynthetic :: [Attribute] -> a -> Maybe a
unlessSynthetic attributes x = if any isAttrSynthetic attributes then Nothing else Just x
where isAttrSynthetic AttrSynthetic = True
isAttrSynthetic _ = False
parseModifiers :: Word16 -> [Modifier]
parseModifiers flags =
catMaybes [0x0001 ~> Public,
0x0002 ~> Private,
0x0004 ~> Protected,
0x0008 ~> Static,
0x0010 ~> Final,
-- TODO: How is this represented in language-java?
-- 0x0020 ~> Synchronized,
0x0040 ~> Volatile,
0x0080 ~> Transient,
0x0100 ~> Native,
0x0400 ~> Abstract,
0x0800 ~> StrictFP]
where flag ~> mod | flag .~. flags = Just mod
| otherwise = Nothing
getModifiers = parseModifiers <$> lift getWord16be
getToplevel =
do flags <- lift getWord16be
let iface = 0x0200 .~. flags
modifiers = parseModifiers flags
return (iface, modifiers)
-- | Descriptor of an inner (nested) type.
--
-- The actual definition of inner types are not in the .class file, but
-- in separate ones for each type. E.g. if class @Foo@ is defined
-- inside class @Bar@, then the file @Bar.class@ will contain an
-- 'InnerType' for Foo, and the actual definition is in
-- @Bar$Foo.class@.
data InnerType = InnerType [Modifier] ClassType
deriving Show
data ClassImportResult = ClassImportResult
{ sourcePath :: Maybe String,
outerType :: Maybe ClassType,
typeDecl :: TypeDecl,
innerTypes :: [InnerType] }
deriving Show
instance NFData Constant where
rnf (ConstantClass ty) = ty `seq` ()
rnf (ConstantValue value) = value `deepseq` ()
rnf (ConstantString s) = s `deepseq` ()
instance NFData ConstantValue where
rnf (ValueString s) = s `deepseq` ()
rnf (ValueInteger n) = n `seq` ()
rnf (ValueLong n) = n `seq` ()
rnf (ValueFloat f) = f `seq` ()
rnf (ValueDouble f) = f `seq` ()
mapFirst f = getFirst . mconcat . map (First . f)
-- | Parse a binary .class file into an 'ImportedType'.
getClass :: Get (ClassType, ClassImportResult)
getClass =
do (_major, _minor) <- getHeader
constTable <- getConstants
constTable `deepseq` flip runReaderT constTable $
do (isInterface, modifiers) <- getToplevel
classType@(ClassType parts) <- getClassType
let (this, _) = last parts
superClass <- getClassTypeMaybe
let super = case superClass of
Nothing -> Nothing
Just superClass -> if superClass /= classObject then Just $ ClassRefType superClass else Nothing
ifaces <- getMany (ClassRefType <$> getClassType)
fields <- catMaybes <$> getMany getField
methods <- catMaybes <$> getMany (getMethod this)
attributes <- getAttributes
let sourcePath = mapFirst getSourceFile attributes
where getSourceFile (AttrSourceFile path) = Just path
getSourceFile _ = Nothing
let innerClasses = fromMaybe [] $ mapFirst getInnerClasses attributes
where getInnerClasses (AttrInnerClasses ics) = Just ics
getInnerClasses _ = Nothing
innerDecls = mapMaybe (toInnerType classType) innerClasses
outer = getOuter classType innerClasses
let members = fields ++ methods
decl = if isInterface
then InterfaceTypeDecl $ InterfaceDecl modifiers this [] ifaces (InterfaceBody members)
else ClassTypeDecl $ ClassDecl modifiers this [] super ifaces (ClassBody $ map MemberDecl members)
return (classType, ClassImportResult sourcePath outer decl innerDecls)
where toInnerType classType ic =
do outer <- innerOuter ic
guard $ outer == classType
let inner = innerInner ic
modifiers = parseModifiers $ innerFlags ic
return $ InnerType modifiers inner
-- (ClassDecl _ id tys super ifaces body) `withClassModifiers` ms = ClassDecl ms id tys super ifaces body
-- (InterfaceDecl _ id tys ifaces body) `withInterfaceModifiers` ms = InterfaceDecl ms id tys ifaces body
getOuter classType ics = innerOuter =<< find (\ ic -> innerInner ic == classType) ics
-- WIP:
--
-- type DeclMap = [(ClassType, ImportedType)]
-- -- | Parse a bunch of binary .class files
-- --
-- -- Because inner class declarations are actually described in separate .class files,
-- -- this function needs all relevant .class files. Inner classes in the result are
-- -- represented as proper 'MemberDecl's.
-- parseClasses :: [ByteString] -> [(ClassType, TypeDecl)]
-- parseClasses streams = mapMaybe toTypeDecl declMap
-- where declMap = map run streams
-- run stream = runGet (getClass_ $ Just declMap) stream
-- toTypeDecl :: (ClassType, ImportedType) -> Maybe (ClassType, TypeDecl)
-- toTypeDecl (classType, (Toplevel decl)) = Just (classType, decl)
-- toTypeDecl _ = Nothing
-- -- | Parse all classes in a .jar file, and return a list of 'CompilationUnit's corresponding to non-inner classes.
-- parseJar :: FilePath -> IO [CompilationUnit]
-- parseJar jarPath = undefined
You can’t perform that action at this time.
