Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions bin/deepstate/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Copyright (c) 2019 Trail of Bits, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# you may not use this file except in compliance with the License.d
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
Expand Down Expand Up @@ -182,7 +182,19 @@ def parse_args(cls) -> Optional[argparse.Namespace]:

# if configuration is specified, parse and replace argument instantiations
if args.config:
_args.update(cls.build_from_config(args.config)) # type: ignore
# Build a type map from argparse's registered actions so config values
# get coerced to the correct types (fixes issue #311: configparser returns
# all values as strings, causing TypeError
type_map: Dict[str, Any] = {}
for action in parser._actions:
if action.dest and action.type is not None:
type_map[action.dest] = action.type
elif action.dest and isinstance(action, argparse._StoreTrueAction):
type_map[action.dest] = lambda v: v.strip().lower() in ('true', '1', 'yes')
elif action.dest and isinstance(action, argparse._StoreFalseAction):
type_map[action.dest] = lambda v: v.strip().lower() not in ('true', '1', 'yes')

_args.update(cls.build_from_config(args.config, type_map=type_map)) # type: ignore

# Cleanup: force --no_exit_compile to be on, meaning if user specifies a `[test]` section,
# execution will continue. Delete config as well
Expand All @@ -206,8 +218,7 @@ def parse_args(cls) -> Optional[argparse.Namespace]:


@staticmethod
def build_from_config(config: str, allowed_keys: Optional[List[str]] = None, include_sections: bool = False) -> Union[Dict[str, Dict[str, Any]], Dict[str, Any]]:
"""
def build_from_config(config: str, allowed_keys: Optional[List[str]] = None, include_sections: bool = False, type_map: Optional[Dict[str, Any]] = None) -> Union[Dict[str, Dict[str, Any]], Dict[str, Any]]: """
Simple auxiliary helper that does safe and correct parsing of DeepState configurations. This can be used
in the following manners:

Expand All @@ -218,6 +229,8 @@ def build_from_config(config: str, allowed_keys: Optional[List[str]] = None, inc
:param config: path to configuration file
:param allowed_keys: contains allowed keys that should be parsed
:param include_sections: if true, parse all sections, and return a Dict[str, Dict[str, Any]] where keys are section names
:param type_map: optional dict mapping argument names to their expected types (e.g. {"timeout": int}).
Used to coerce config file string values to correct types.
"""

context: Dict[str, Dict[str, Any]] = dict() # type: ignore
Expand Down Expand Up @@ -261,10 +274,25 @@ def build_from_config(config: str, allowed_keys: Optional[List[str]] = None, inc
if key not in allowed_keys:
continue

if isinstance(val, list):
#if isinstance(val, list):
# _context[key].append(val)
#else:
# _context[key] = val
if isinstance(val, list): ## Replaced above with below from lines 269 - 283, applies type coercion
_context[key].append(val)
else:
_context[key] = val
# Coerce the string value to the expected type if a type_map is provided
if type_map is not None and key in type_map:
expected_type = type_map[key]
try:
_context[key] = expected_type(val)
except (ValueError, TypeError):
raise AnalysisBackendError(
f"Config file error: cannot convert '{key} = {val}' "
f"to expected type '{expected_type.__name__}'"
)
else:
_context[key] = val

return context # type: ignore

Expand Down