<commit_before>{-# LANGUAGE OverloadedStrings #-}
module Tests.Dalvik.Labels ( tests ) where

import qualified Data.List as L
import qualified Data.Map as M
import Data.Maybe ( fromMaybe )
import Dalvik.Instruction
import qualified Dalvik.Types as DT
import Dalvik.SSA ( labelMethod )
import Dalvik.SSA.Labeling

import Tests.Dalvik.DexLoaders ( DexReader, memoIO, readAsDex, getEncodedMethod )

import System.FilePath ((</>))
import Text.Printf (printf)

import Test.Framework as T
import Test.Framework.Providers.HUnit as T
import qualified Test.HUnit as T

javaInputs :: FilePath
javaInputs = "./tests/testfiles/javaInputs"

tests :: Test
tests = T.buildTest $ do
  getDex <- memoIO readAsDex
  return $ T.testGroup "SSA Label tests" $ map (getLabelTests getDex (javaInputs </> "LabelTests.java"))
    [ ("LLabelTests;", "localCopies", "(II)I", ArgumentLabel "%arg1" 1)
    , ("LLabelTests;", "simpleBranch", "(II)I", PhiLabel 1 [0,2] 7)
    , ("LLabelTests;", "simplePackedSwitch", "(III)I", PhiLabel 2 [0,1,3] 9)
    , ("LLabelTests;", "simpleSparseSwitch", "(III)I", PhiLabel 2 [0,1,3] 9)
    , ("LLabelTests;", "simpleLoop", "(II)I", PhiLabel 1 [0,2] 8)
    , ("LLabelTests;", "loopNopBody", "(II)I", ArgumentLabel "%arg1" 1)
    , ("LLabelTests;", "uncaughtNPE", "([Ljava/lang/String;)I", SimpleLabel 4)
    , ("LLabelTests;", "simpleNPECatchNPE", "([Ljava/lang/String;)I", PhiLabel 1 [0,2] 5)
    , ("LLabelTests;", "simpleNPECatchRuntimeEx", "([Ljava/lang/String;)I", PhiLabel 1 [0,2] 5)
    , ("LLabelTests;", "simpleNPECatchException", "([Ljava/lang/String;)I", PhiLabel 1 [0,2] 5)
    , ("LLabelTests;", "simpleNPECatchThrowable", "([Ljava/lang/String;)I", PhiLabel 1 [0,2] 5)
    , ("LLabelTests;", "simpleNPECatchArithEx", "([Ljava/lang/String;)I", SimpleLabel 4)
    , ("LLabelTests;", "simpleNPEOnlyFirstHandler", "([Ljava/lang/String;)I", PhiLabel 1 [0,2] 5)
    , ("LLabelTests;", "simpleNPEWithHandlerAndFinally", "([Ljava/lang/String;)I", SimpleLabel 4)
    , ("LLabelTests;", "divideNoCatch", "(II)I", SimpleLabel 6)
    , ("LLabelTests;", "arithNoDivision", "(II)I", SimpleLabel 9)
    , ("LLabelTests;", "safeDivideWithCatch", "(II)I", PhiLabel 2 [1,4,5] 7)
    , ("LLabelTests;", "divisionCatchArithEx", "(II)I", PhiLabel 1 [0,2] 7)
    , ("LLabelTests;", "divisionCatchRuntimeEx", "(II)I", PhiLabel 1 [0,2] 7)
    , ("LLabelTests;", "divisionCatchException", "(II)I", PhiLabel 1 [0,2] 7)
    , ("LLabelTests;", "divisionCatchThrowable", "(II)I", PhiLabel 1 [0,2] 7)
    , ("LLabelTests;", "divisionCatchNPE", "(II)I", SimpleLabel 6)
    , ("LLabelTests;", "checkCastNoHandler", "(Ljava/lang/Object;)I", SimpleLabel 4)
    , ("LLabelTests;", "checkCastHandleCCE", "(Ljava/lang/Object;)I", PhiLabel 2 [1,3] 5)
    , ("LLabelTests;", "checkCastHandleRuntimeException", "(Ljava/lang/Object;)I", PhiLabel 2 [1,3] 5)
    , ("LLabelTests;", "checkCastHandleException", "(Ljava/lang/Object;)I", PhiLabel 2 [1,3] 5)
    , ("LLabelTests;", "checkCastHandleThrowable", "(Ljava/lang/Object;)I", PhiLabel 2 [1,3] 5)
    , ("LLabelTests;", "checkCastHandleArithException", "(Ljava/lang/Object;)I", SimpleLabel 4)
    , ("LLabelTests;", "invokeToAllHandlers", "(Ljava/lang/Object;)I", PhiLabel 3 [2,4,5,6,7,8] 5)
    , ("LLabelTests;", "returnThisFieldNoHandler", "()I", SimpleLabel 2)
      -- Note that, ideally, we could use a bit more static analysis to make this
      -- test fail in a good way (since @this@ can never be NULL).
    , ("LLabelTests;", "returnThisFieldHandler", "()I", PhiLabel 1 [0,2] 3)
    , ("LLabelTests;", "returnOtherFieldNoHandler", "(LLabelTests;)I", SimpleLabel 4)
    , ("LLabelTests;", "returnOtherFieldHandler", "(LLabelTests;)I", PhiLabel 1 [0,2] 5)
    , ("LLabelTests;", "arrayReadNoHandler", "([II)I", SimpleLabel 6)
    , ("LLabelTests;", "arrayReadHandler", "([II)I", PhiLabel 1 [0,2] 7)
    , ("LLabelTests;", "arrayReadHandlerThrowable", "([II)I", PhiLabel 1 [0,2] 7)
    , ("LLabelTests;", "arrayWriteNoHandler", "([Ljava/lang/Object;Ljava/lang/Object;I)I", ArgumentLabel "%arg0" 3)
    , ("LLabelTests;", "arrayWriteHandler", "([Ljava/lang/Object;Ljava/lang/Object;I)I", PhiLabel 1 [0,2,3] 8)
    ]
-- FIXME: Add an extra oracle parameter, a list of phi incoming
-- values.  It is only checked if the label is a matching phi label.
isReturn :: Labeling -> Int -> Bool
isReturn l ix =
  case labelingInstructionAt l ix of
    Just (Return _ _) -> True
    _ -> False

checkReturnValue :: Labeling -> Label -> T.Assertion
checkReturnValue l expected = fromMaybe (T.assertFailure "No return instruction found") $ do
  (_, m) <- L.find (isReturn l . fst) instMaps
  case M.toList m of
    [(_, label) ] -> return $ do
      T.assertBool "Non-unique value label" (generatedLabelsAreUnique l)
      T.assertEqual "Unexpected label mismatch" expected label
    _ -> return $ T.assertFailure "More than one (or no) register mapped for return"
  where
    instMaps = M.toList (labelingReadRegs l)

getLabelTests :: DexReader -> FilePath -> (String, String, String, Label) -> Test
getLabelTests getDex file (klass, method, sig, oracle) =
  testWithDexFile getDex ("CheckReturn: " ++ toStr klass method sig) file $ \dexFile ->
    case getEncodedMethod dexFile klass method sig of
      Nothing -> T.assertFailure ("Could not find method: " ++ toStr klass method sig)
      Just m ->
        case labelMethod dexFile m of
          Left e -> T.assertFailure ("Could not label method: " ++ toStr klass method sig ++ " " ++ DT.decodeErrorAsString e)
          Right lbls -> checkReturnValue lbls oracle

toStr :: String -> String -> String -> String
toStr []      m sig = printf "%s%s" m sig
toStr c@(_:_) m sig | last c /= ';' = printf "%s%s%s" c m sig
                    | otherwise     = let
                      pkgName = (init c) ++ "."
                      in printf "%s%s%s" pkgName m sig

testWithDexFile :: DexReader -> String -> FilePath -> (DT.DexFile -> T.Assertion) -> Test
testWithDexFile getDex descr file fn = T.testCase descr $ do
  eDexFile <- getDex file
  case eDexFile of
    Left err      -> T.assertFailure err
    Right dexFile -> fn dexFile<commit_msg>Make the test suite for labeling more stringent.<commit_after>{-# LANGUAGE OverloadedStrings #-}
module Tests.Dalvik.Labels ( tests ) where

import qualified Data.List as L
import qualified Data.Map as M
import Data.Maybe ( fromMaybe )
import qualified Data.Set as S
import Dalvik.Instruction
import qualified Dalvik.Types as DT
import Dalvik.SSA ( labelMethod )
import Dalvik.SSA.Labeling

import Tests.Dalvik.DexLoaders ( DexReader, memoIO, readAsDex, getEncodedMethod )

import System.FilePath ((</>))
import Text.Printf (printf)

import Test.Framework as T
import Test.Framework.Providers.HUnit as T
import qualified Test.HUnit as T

javaInputs :: FilePath
javaInputs = "./tests/testfiles/javaInputs"

tests :: Test
tests = T.buildTest $ do
  getDex <- memoIO readAsDex
  return $ T.testGroup "SSA Label tests" $ map (getLabelTests getDex (javaInputs </> "LabelTests.java"))
    [ ("LLabelTests;", "localCopies", "(II)I", ArgumentLabel "%arg1" 1, [])
    , ("LLabelTests;", "simpleBranch", "(II)I", PhiLabel 1 [0,2] 7,
       [ArgumentLabel "%arg0" 2,ArgumentLabel "%arg1" 1])
    , ("LLabelTests;", "simplePackedSwitch", "(III)I", PhiLabel 2 [0,1,3] 9,
       [SimpleLabel 8,ArgumentLabel "%arg0" 3,ArgumentLabel "%arg1" 2])
    , ("LLabelTests;", "simpleSparseSwitch", "(III)I", PhiLabel 2 [0,1,3] 9,
       [SimpleLabel 8,ArgumentLabel "%arg0" 3,ArgumentLabel "%arg1" 2])
    , ("LLabelTests;", "simpleLoop", "(II)I", PhiLabel 1 [0,2] 8,
       [SimpleLabel 9,ArgumentLabel "%arg1" 1])
    , ("LLabelTests;", "loopNopBody", "(II)I", ArgumentLabel "%arg1" 1, [])
    , ("LLabelTests;", "uncaughtNPE", "([Ljava/lang/String;)I", SimpleLabel 4, [])
    , ("LLabelTests;", "simpleNPECatchNPE", "([Ljava/lang/String;)I", PhiLabel 1 [0,2] 5,
       [SimpleLabel 4, SimpleLabel 7])
    , ("LLabelTests;", "simpleNPECatchRuntimeEx", "([Ljava/lang/String;)I", PhiLabel 1 [0,2] 5,
       [SimpleLabel 4, SimpleLabel 7])
    , ("LLabelTests;", "simpleNPECatchException", "([Ljava/lang/String;)I", PhiLabel 1 [0,2] 5,
       [SimpleLabel 4, SimpleLabel 7])
    , ("LLabelTests;", "simpleNPECatchThrowable", "([Ljava/lang/String;)I", PhiLabel 1 [0,2] 5,
       [SimpleLabel 4, SimpleLabel 7])
    , ("LLabelTests;", "simpleNPECatchArithEx", "([Ljava/lang/String;)I", SimpleLabel 4, [])
    , ("LLabelTests;", "simpleNPEOnlyFirstHandler", "([Ljava/lang/String;)I", PhiLabel 1 [0,2] 5,
       [SimpleLabel 4, SimpleLabel 7])
    , ("LLabelTests;", "simpleNPEWithHandlerAndFinally", "([Ljava/lang/String;)I", SimpleLabel 4, [])
    , ("LLabelTests;", "divideNoCatch", "(II)I", SimpleLabel 6, [])
    , ("LLabelTests;", "arithNoDivision", "(II)I", SimpleLabel 9, [])
    , ("LLabelTests;", "safeDivideWithCatch", "(II)I", PhiLabel 2 [1,4,5] 7,
       [SimpleLabel 6, SimpleLabel 8, SimpleLabel 10])
    , ("LLabelTests;", "divisionCatchArithEx", "(II)I", PhiLabel 1 [0,2] 7,
       [SimpleLabel 6, SimpleLabel 9])
    , ("LLabelTests;", "divisionCatchRuntimeEx", "(II)I", PhiLabel 1 [0,2] 7,
       [SimpleLabel 6, SimpleLabel 9])
    , ("LLabelTests;", "divisionCatchException", "(II)I", PhiLabel 1 [0,2] 7,
       [SimpleLabel 6, SimpleLabel 9])
    , ("LLabelTests;", "divisionCatchThrowable", "(II)I", PhiLabel 1 [0,2] 7,
       [SimpleLabel 6, SimpleLabel 9])
    , ("LLabelTests;", "divisionCatchNPE", "(II)I", SimpleLabel 6, [])
    , ("LLabelTests;", "checkCastNoHandler", "(Ljava/lang/Object;)I", SimpleLabel 4, [])
    , ("LLabelTests;", "checkCastHandleCCE", "(Ljava/lang/Object;)I", PhiLabel 2 [1,3] 5,
       [SimpleLabel 4, SimpleLabel 7])
    , ("LLabelTests;", "checkCastHandleRuntimeException", "(Ljava/lang/Object;)I", PhiLabel 2 [1,3] 5,
       [SimpleLabel 4, SimpleLabel 7])
    , ("LLabelTests;", "checkCastHandleException", "(Ljava/lang/Object;)I", PhiLabel 2 [1,3] 5,
       [SimpleLabel 4, SimpleLabel 7])
    , ("LLabelTests;", "checkCastHandleThrowable", "(Ljava/lang/Object;)I", PhiLabel 2 [1,3] 5,
       [SimpleLabel 4, SimpleLabel 7])
    , ("LLabelTests;", "checkCastHandleArithException", "(Ljava/lang/Object;)I", SimpleLabel 4, [])
    , ("LLabelTests;", "invokeToAllHandlers", "(Ljava/lang/Object;)I", PhiLabel 3 [2,4,5,6,7,8] 5,
       [SimpleLabel 4, SimpleLabel 7, SimpleLabel 9, SimpleLabel 11, SimpleLabel 13, SimpleLabel 15])
    , ("LLabelTests;", "returnThisFieldNoHandler", "()I", SimpleLabel 2, [])
      -- Note that, ideally, we could use a bit more static analysis to make this
      -- test fail in a good way (since @this@ can never be NULL).
    , ("LLabelTests;", "returnThisFieldHandler", "()I", PhiLabel 1 [0,2] 3,
       [SimpleLabel 2, SimpleLabel 5])
    , ("LLabelTests;", "returnOtherFieldNoHandler", "(LLabelTests;)I", SimpleLabel 4, [])
    , ("LLabelTests;", "returnOtherFieldHandler", "(LLabelTests;)I", PhiLabel 1 [0,2] 5,
       [SimpleLabel 4, SimpleLabel 7])
    , ("LLabelTests;", "arrayReadNoHandler", "([II)I", SimpleLabel 6, [])
    , ("LLabelTests;", "arrayReadHandler", "([II)I", PhiLabel 1 [0,2] 7,
       [SimpleLabel 6, SimpleLabel 9])
    , ("LLabelTests;", "arrayReadHandlerThrowable", "([II)I", PhiLabel 1 [0,2] 7,
       [SimpleLabel 6, SimpleLabel 9])
    , ("LLabelTests;", "arrayWriteNoHandler", "([Ljava/lang/Object;Ljava/lang/Object;I)I", ArgumentLabel "%arg0" 3, [])
    , ("LLabelTests;", "arrayWriteHandler", "([Ljava/lang/Object;Ljava/lang/Object;I)I", PhiLabel 1 [0,2,3] 8,
       [SimpleLabel 10, SimpleLabel 12, ArgumentLabel "%arg0" 3])
    ]
-- FIXME: Add an extra oracle parameter, a list of phi incoming
-- values.  It is only checked if the label is a matching phi label.
isReturn :: Labeling -> Int -> Bool
isReturn l ix =
  case labelingInstructionAt l ix of
    Just (Return _ _) -> True
    _ -> False

checkReturnValue :: Labeling -> Label -> [Label] -> T.Assertion
checkReturnValue l expected phiExpected = fromMaybe (T.assertFailure "No return instruction found") $ do
  (_, m) <- L.find (isReturn l . fst) instMaps
  case M.toList m of
    [(_, label) ] -> return $ do
      T.assertBool "Non-unique value label" (generatedLabelsAreUnique l)
      T.assertEqual "Unexpected label mismatch" expected label
      case label of
        PhiLabel _ _ _ -> do
          -- If we have a phi node, make sure it is referencing the correct values.
          let Just lbls = M.lookup label (labelingPhis l)
          T.assertEqual "Phi value mismatch" lbls (S.fromList phiExpected)
          T.assertBool "Phi label requires more than one incoming value" (length phiExpected > 1)
        _ -> T.assertEqual "There should not be expected values for non-phis" [] phiExpected
    _ -> return $ T.assertFailure "More than one (or no) register mapped for return"
  where
    instMaps = M.toList (labelingReadRegs l)

getLabelTests :: DexReader -> FilePath -> (String, String, String, Label, [Label]) -> Test
getLabelTests getDex file (klass, method, sig, oracle, oracle') =
  testWithDexFile getDex ("CheckReturn: " ++ toStr klass method sig) file $ \dexFile ->
    case getEncodedMethod dexFile klass method sig of
      Nothing -> T.assertFailure ("Could not find method: " ++ toStr klass method sig)
      Just m ->
        case labelMethod dexFile m of
          Left e -> T.assertFailure ("Could not label method: " ++ toStr klass method sig ++ " " ++ DT.decodeErrorAsString e)
          Right lbls -> checkReturnValue lbls oracle oracle'

toStr :: String -> String -> String -> String
toStr []      m sig = printf "%s%s" m sig
toStr c@(_:_) m sig | last c /= ';' = printf "%s%s%s" c m sig
                    | otherwise     = let
                      pkgName = (init c) ++ "."
                      in printf "%s%s%s" pkgName m sig

testWithDexFile :: DexReader -> String -> FilePath -> (DT.DexFile -> T.Assertion) -> Test
testWithDexFile getDex descr file fn = T.testCase descr $ do
  eDexFile <- getDex file
  case eDexFile of
    Left err      -> T.assertFailure err
    Right dexFile -> fn dexFile
{'fraction_non_alphanumeric': 0.16202712348322626, 'fraction_numerical': 0.022341184867951464, 'mean_word_length': 3.9438955539872973, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 63, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2469516', 'n_tokens_mistral': 5055, 'n_tokens_neox': 4813, 'n_words': 1323}