Include exit codes in DFA (ref #2541)
This commit is contained in:
parent
819470fa1d
commit
b261ec24f9
|
@ -651,7 +651,10 @@ build t = do
|
||||||
pg <- wordToExactPseudoGlob c
|
pg <- wordToExactPseudoGlob c
|
||||||
return $ pg `pseudoGlobIsSuperSetof` [PGMany]
|
return $ pg `pseudoGlobIsSuperSetof` [PGMany]
|
||||||
|
|
||||||
T_Condition _ _ op -> build op
|
T_Condition id _ op -> do
|
||||||
|
cond <- build op
|
||||||
|
status <- newNodeRange $ CFSetExitCode id
|
||||||
|
linkRange cond status
|
||||||
|
|
||||||
T_CoProc id maybeName t -> do
|
T_CoProc id maybeName t -> do
|
||||||
let name = fromMaybe "COPROC" maybeName
|
let name = fromMaybe "COPROC" maybeName
|
||||||
|
@ -798,7 +801,8 @@ build t = do
|
||||||
start <- newStructuralNode
|
start <- newStructuralNode
|
||||||
hasLastpipe <- reader $ cfLastpipe . cfParameters
|
hasLastpipe <- reader $ cfLastpipe . cfParameters
|
||||||
(leading, last) <- buildPipe hasLastpipe cmds
|
(leading, last) <- buildPipe hasLastpipe cmds
|
||||||
end <- newStructuralNode
|
-- Ideally we'd let this exit code be that of the last command in the pipeline but ok
|
||||||
|
end <- newNodeRange $ CFSetExitCode id
|
||||||
|
|
||||||
mapM_ (linkRange start) leading
|
mapM_ (linkRange start) leading
|
||||||
mapM_ (\c -> linkRangeAs CFEFalseFlow c end) leading
|
mapM_ (\c -> linkRangeAs CFEFalseFlow c end) leading
|
||||||
|
|
|
@ -104,11 +104,29 @@ data CFGAnalysis = CFGAnalysis {
|
||||||
|
|
||||||
-- The program state we expose externally
|
-- The program state we expose externally
|
||||||
data ProgramState = ProgramState {
|
data ProgramState = ProgramState {
|
||||||
-- internalState :: InternalState, -- For debugging
|
-- internalState :: InternalState, -- For debugging
|
||||||
variablesInScope :: M.Map String VariableState,
|
variablesInScope :: M.Map String VariableState,
|
||||||
|
exitCodes :: S.Set Id,
|
||||||
stateIsReachable :: Bool
|
stateIsReachable :: Bool
|
||||||
} deriving (Show, Eq, Generic, NFData)
|
} deriving (Show, Eq, Generic, NFData)
|
||||||
|
|
||||||
|
internalToExternal :: InternalState -> ProgramState
|
||||||
|
internalToExternal s =
|
||||||
|
ProgramState {
|
||||||
|
-- Censor the literal value to avoid introducing dependencies on it. It's just for debugging.
|
||||||
|
variablesInScope = M.map censor flatVars,
|
||||||
|
-- internalState = s, -- For debugging
|
||||||
|
exitCodes = fromMaybe S.empty $ sExitCodes s,
|
||||||
|
stateIsReachable = fromMaybe True $ sIsReachable s
|
||||||
|
}
|
||||||
|
where
|
||||||
|
censor s = s {
|
||||||
|
variableValue = (variableValue s) {
|
||||||
|
literalValue = Nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flatVars = M.unionsWith (\_ last -> last) $ map mapStorage [sGlobalValues s, sLocalValues s, sPrefixValues s]
|
||||||
|
|
||||||
-- Conveniently get the state before a token id
|
-- Conveniently get the state before a token id
|
||||||
getIncomingState :: CFGAnalysis -> Id -> Maybe ProgramState
|
getIncomingState :: CFGAnalysis -> Id -> Maybe ProgramState
|
||||||
getIncomingState analysis id = do
|
getIncomingState analysis id = do
|
||||||
|
@ -130,6 +148,7 @@ data InternalState = InternalState {
|
||||||
sLocalValues :: VersionedMap String VariableState,
|
sLocalValues :: VersionedMap String VariableState,
|
||||||
sPrefixValues :: VersionedMap String VariableState,
|
sPrefixValues :: VersionedMap String VariableState,
|
||||||
sFunctionTargets :: VersionedMap String FunctionValue,
|
sFunctionTargets :: VersionedMap String FunctionValue,
|
||||||
|
sExitCodes :: Maybe (S.Set Id),
|
||||||
sIsReachable :: Maybe Bool
|
sIsReachable :: Maybe Bool
|
||||||
} deriving (Show, Generic, NFData)
|
} deriving (Show, Generic, NFData)
|
||||||
|
|
||||||
|
@ -139,6 +158,7 @@ newInternalState = InternalState {
|
||||||
sLocalValues = vmEmpty,
|
sLocalValues = vmEmpty,
|
||||||
sPrefixValues = vmEmpty,
|
sPrefixValues = vmEmpty,
|
||||||
sFunctionTargets = vmEmpty,
|
sFunctionTargets = vmEmpty,
|
||||||
|
sExitCodes = Nothing,
|
||||||
sIsReachable = Nothing
|
sIsReachable = Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,31 +216,25 @@ removeProperties props state = state {
|
||||||
variableProperties = S.map (\s -> S.difference s props) $ variableProperties state
|
variableProperties = S.map (\s -> S.difference s props) $ variableProperties state
|
||||||
}
|
}
|
||||||
|
|
||||||
internalToExternal :: InternalState -> ProgramState
|
setExitCode id = setExitCodes (S.singleton id)
|
||||||
internalToExternal s =
|
setExitCodes set state = modified state {
|
||||||
ProgramState {
|
sExitCodes = Just $ set
|
||||||
-- Censor the literal value to avoid introducing dependencies on it. It's just for debugging.
|
}
|
||||||
variablesInScope = M.map censor flatVars,
|
|
||||||
-- internalState = s, -- For debugging
|
|
||||||
stateIsReachable = fromMaybe True $ sIsReachable s
|
|
||||||
}
|
|
||||||
where
|
|
||||||
censor s = s {
|
|
||||||
variableValue = (variableValue s) {
|
|
||||||
literalValue = Nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
flatVars = M.unionsWith (\_ last -> last) $ map mapStorage [sGlobalValues s, sLocalValues s, sPrefixValues s]
|
|
||||||
|
|
||||||
-- Dependencies on values, e.g. "if there is a global variable named 'foo' without spaces"
|
-- Dependencies on values, e.g. "if there is a global variable named 'foo' without spaces"
|
||||||
-- This is used to see if the DFA of a function would result in the same state, so anything
|
-- This is used to see if the DFA of a function would result in the same state, so anything
|
||||||
-- that affects DFA must be tracked.
|
-- that affects DFA must be tracked.
|
||||||
data StateDependency =
|
data StateDependency =
|
||||||
|
-- Complete variable state
|
||||||
DepState Scope String VariableState
|
DepState Scope String VariableState
|
||||||
|
-- Only variable properties (we need properties but not values for x=1)
|
||||||
| DepProperties Scope String VariableProperties
|
| DepProperties Scope String VariableProperties
|
||||||
|
-- Function definition
|
||||||
| DepFunction String (S.Set FunctionDefinition)
|
| DepFunction String (S.Set FunctionDefinition)
|
||||||
-- Whether invoking the node would result in recursion (i.e., is the function on the stack?)
|
-- Whether invoking the node would result in recursion (i.e., is the function on the stack?)
|
||||||
| DepIsRecursive Node Bool
|
| DepIsRecursive Node Bool
|
||||||
|
-- The set of commands that could have provided the exit code $?
|
||||||
|
| DepExitCodes (S.Set Id)
|
||||||
deriving (Show, Eq, Ord, Generic, NFData)
|
deriving (Show, Eq, Ord, Generic, NFData)
|
||||||
|
|
||||||
-- A function definition, or lack thereof
|
-- A function definition, or lack thereof
|
||||||
|
@ -242,6 +256,7 @@ depsToState set = foldl insert newInternalState $ S.toList set
|
||||||
-- State includes properties and more, so don't overwrite a state with properties
|
-- State includes properties and more, so don't overwrite a state with properties
|
||||||
DepProperties scope name props -> insertIn False scope name unknownVariableState { variableProperties = props } state
|
DepProperties scope name props -> insertIn False scope name unknownVariableState { variableProperties = props } state
|
||||||
DepIsRecursive _ _ -> state
|
DepIsRecursive _ _ -> state
|
||||||
|
DepExitCodes s -> setExitCodes s state
|
||||||
|
|
||||||
insertIn overwrite scope name val state =
|
insertIn overwrite scope name val state =
|
||||||
let
|
let
|
||||||
|
@ -400,6 +415,7 @@ patchState base diff =
|
||||||
sLocalValues = vmPatch (sLocalValues base) (sLocalValues diff),
|
sLocalValues = vmPatch (sLocalValues base) (sLocalValues diff),
|
||||||
sPrefixValues = vmPatch (sPrefixValues base) (sPrefixValues diff),
|
sPrefixValues = vmPatch (sPrefixValues base) (sPrefixValues diff),
|
||||||
sFunctionTargets = vmPatch (sFunctionTargets base) (sFunctionTargets diff),
|
sFunctionTargets = vmPatch (sFunctionTargets base) (sFunctionTargets diff),
|
||||||
|
sExitCodes = sExitCodes diff `mplus` sExitCodes base,
|
||||||
sIsReachable = sIsReachable diff `mplus` sIsReachable base
|
sIsReachable = sIsReachable diff `mplus` sIsReachable base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -444,12 +460,14 @@ mergeState ctx a b = do
|
||||||
locals <- mergeMaps ctx mergeVariableState readVariable (sLocalValues a) (sLocalValues b)
|
locals <- mergeMaps ctx mergeVariableState readVariable (sLocalValues a) (sLocalValues b)
|
||||||
prefix <- mergeMaps ctx mergeVariableState readVariable (sPrefixValues a) (sPrefixValues b)
|
prefix <- mergeMaps ctx mergeVariableState readVariable (sPrefixValues a) (sPrefixValues b)
|
||||||
funcs <- mergeMaps ctx S.union readFunction (sFunctionTargets a) (sFunctionTargets b)
|
funcs <- mergeMaps ctx S.union readFunction (sFunctionTargets a) (sFunctionTargets b)
|
||||||
|
exitCodes <- mergeMaybes ctx S.union readExitCodes (sExitCodes a) (sExitCodes b)
|
||||||
return $ InternalState {
|
return $ InternalState {
|
||||||
sVersion = -1,
|
sVersion = -1,
|
||||||
sGlobalValues = globals,
|
sGlobalValues = globals,
|
||||||
sLocalValues = locals,
|
sLocalValues = locals,
|
||||||
sPrefixValues = prefix,
|
sPrefixValues = prefix,
|
||||||
sFunctionTargets = funcs,
|
sFunctionTargets = funcs,
|
||||||
|
sExitCodes = exitCodes,
|
||||||
sIsReachable = liftM2 (&&) (sIsReachable a) (sIsReachable b)
|
sIsReachable = liftM2 (&&) (sIsReachable a) (sIsReachable b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,6 +511,18 @@ mergeMaps ctx merger reader a b =
|
||||||
nv1 <- reader ctx k2
|
nv1 <- reader ctx k2
|
||||||
f ((k2, merger nv1 v2):l) l1 rest2
|
f ((k2, merger nv1 v2):l) l1 rest2
|
||||||
|
|
||||||
|
-- Merge two Maybes, like mergeMaps for a single element
|
||||||
|
mergeMaybes ctx merger reader a b =
|
||||||
|
case (a, b) of
|
||||||
|
(Nothing, Nothing) -> return Nothing
|
||||||
|
(Just v1, Nothing) -> single v1
|
||||||
|
(Nothing, Just v2) -> single v2
|
||||||
|
(Just v1, Just v2) -> return $ Just $ merger v1 v2
|
||||||
|
where
|
||||||
|
single val = do
|
||||||
|
result <- merger val <$> reader ctx
|
||||||
|
return $ Just result
|
||||||
|
|
||||||
vmFromMap ctx map = return $ VersionedMap {
|
vmFromMap ctx map = return $ VersionedMap {
|
||||||
mapVersion = -1,
|
mapVersion = -1,
|
||||||
mapStorage = map
|
mapStorage = map
|
||||||
|
@ -708,6 +738,12 @@ readFunction ctx name = lookupStack get dep def ctx name
|
||||||
writeFunction ctx name val = do
|
writeFunction ctx name val = do
|
||||||
modifySTRef (cOutput ctx) $ insertFunction name $ S.singleton val
|
modifySTRef (cOutput ctx) $ insertFunction name $ S.singleton val
|
||||||
|
|
||||||
|
readExitCodes ctx = lookupStack get dep def ctx ()
|
||||||
|
where
|
||||||
|
get s () = sExitCodes s
|
||||||
|
def = S.empty
|
||||||
|
dep () v = DepExitCodes v
|
||||||
|
|
||||||
-- Look up each state on the stack until a value is found (or the default is used),
|
-- Look up each state on the stack until a value is found (or the default is used),
|
||||||
-- then add this value as a StateDependency.
|
-- then add this value as a StateDependency.
|
||||||
lookupStack' :: forall s k v.
|
lookupStack' :: forall s k v.
|
||||||
|
@ -872,13 +908,13 @@ transfer ctx label =
|
||||||
CFExecuteCommand cmd -> transferCommand ctx cmd
|
CFExecuteCommand cmd -> transferCommand ctx cmd
|
||||||
CFExecuteSubshell reason entry exit -> transferSubshell ctx reason entry exit
|
CFExecuteSubshell reason entry exit -> transferSubshell ctx reason entry exit
|
||||||
CFApplyEffects effects -> mapM_ (\(IdTagged _ f) -> transferEffect ctx f) effects
|
CFApplyEffects effects -> mapM_ (\(IdTagged _ f) -> transferEffect ctx f) effects
|
||||||
|
CFSetExitCode id -> transferExitCode ctx id
|
||||||
|
|
||||||
CFUnresolvedExit -> patchOutputM ctx unreachableState
|
CFUnresolvedExit -> patchOutputM ctx unreachableState
|
||||||
CFUnreachable -> patchOutputM ctx unreachableState
|
CFUnreachable -> patchOutputM ctx unreachableState
|
||||||
|
|
||||||
-- TODO
|
-- TODO
|
||||||
CFSetBackgroundPid _ -> return ()
|
CFSetBackgroundPid _ -> return ()
|
||||||
CFSetExitCode _ -> return ()
|
|
||||||
CFDropPrefixAssignments {} ->
|
CFDropPrefixAssignments {} ->
|
||||||
modifySTRef (cOutput ctx) $ \c -> modified c { sPrefixValues = vmEmpty }
|
modifySTRef (cOutput ctx) $ \c -> modified c { sPrefixValues = vmEmpty }
|
||||||
-- _ -> error $ "Unknown " ++ show label
|
-- _ -> error $ "Unknown " ++ show label
|
||||||
|
@ -891,8 +927,11 @@ transferSubshell ctx reason entry exit = do
|
||||||
let cout = cOutput ctx
|
let cout = cOutput ctx
|
||||||
initial <- readSTRef cout
|
initial <- readSTRef cout
|
||||||
runCached ctx entry (f entry exit)
|
runCached ctx entry (f entry exit)
|
||||||
|
res <- readSTRef cout
|
||||||
-- Clear subshell changes. TODO: track this to warn about modifications.
|
-- Clear subshell changes. TODO: track this to warn about modifications.
|
||||||
writeSTRef cout initial
|
writeSTRef cout $ initial {
|
||||||
|
sExitCodes = sExitCodes res
|
||||||
|
}
|
||||||
where
|
where
|
||||||
f entry exit ctx = do
|
f entry exit ctx = do
|
||||||
(states, frame) <- withNewStackFrame ctx entry False (flip dataflow $ entry)
|
(states, frame) <- withNewStackFrame ctx entry False (flip dataflow $ entry)
|
||||||
|
@ -947,6 +986,8 @@ transferFunctionValue ctx funcVal =
|
||||||
registerFlowResult ctx entry states deps
|
registerFlowResult ctx entry states deps
|
||||||
return (deps, res)
|
return (deps, res)
|
||||||
|
|
||||||
|
transferExitCode ctx id = do
|
||||||
|
modifySTRef (cOutput ctx) $ setExitCode id
|
||||||
|
|
||||||
-- Register/save the result of a dataflow of a function.
|
-- Register/save the result of a dataflow of a function.
|
||||||
-- At the end, all the different values from different flows are merged together.
|
-- At the end, all the different values from different flows are merged together.
|
||||||
|
@ -1001,8 +1042,10 @@ getCache ctx node = do
|
||||||
-- Transfer a single CFEffect to the output state.
|
-- Transfer a single CFEffect to the output state.
|
||||||
transferEffect ctx effect =
|
transferEffect ctx effect =
|
||||||
case effect of
|
case effect of
|
||||||
CFReadVariable name -> do
|
CFReadVariable name ->
|
||||||
void $ readVariable ctx name
|
case name of
|
||||||
|
"?" -> void $ readExitCodes ctx
|
||||||
|
_ -> void $ readVariable ctx name
|
||||||
CFWriteVariable name value -> do
|
CFWriteVariable name value -> do
|
||||||
val <- cfValueToVariableValue ctx value
|
val <- cfValueToVariableValue ctx value
|
||||||
updateVariableValue ctx name val
|
updateVariableValue ctx name val
|
||||||
|
@ -1235,7 +1278,14 @@ analyzeControlFlow params t =
|
||||||
-- (it's probably not actually dead, just used by a script that sources ours)
|
-- (it's probably not actually dead, just used by a script that sources ours)
|
||||||
let declaredFunctions = getFunctionTargets exitState
|
let declaredFunctions = getFunctionTargets exitState
|
||||||
let uninvoked = M.difference declaredFunctions invokedNodes
|
let uninvoked = M.difference declaredFunctions invokedNodes
|
||||||
analyzeStragglers ctx exitState uninvoked
|
|
||||||
|
let stragglerInput =
|
||||||
|
exitState {
|
||||||
|
-- We don't want `die() { exit $?; }; echo "Sourced"` to assume $? is always echo
|
||||||
|
sExitCodes = Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
analyzeStragglers ctx stragglerInput uninvoked
|
||||||
|
|
||||||
-- Now round up all the states from all data flows
|
-- Now round up all the states from all data flows
|
||||||
-- (FIXME: this excludes functions that were defined in straggling functions)
|
-- (FIXME: this excludes functions that were defined in straggling functions)
|
||||||
|
|
Loading…
Reference in New Issue