crytic_compile.compilation_unit
Module handling the compilation unit Each compilation unit represents a call to the compiler Each compilation unit has one or more source units associated with it At least one compilation unit exists for each version of solc used Maybe more dependending on the framework used (hardhat/foundry/etc)
1""" 2Module handling the compilation unit 3Each compilation unit represents a call to the compiler 4Each compilation unit has one or more source units associated with it 5At least one compilation unit exists for each version of solc used 6 Maybe more dependending on the framework used (hardhat/foundry/etc) 7""" 8import uuid 9from collections import defaultdict 10from typing import TYPE_CHECKING, Dict, List, Set, Optional 11 12from crytic_compile.compiler.compiler import CompilerVersion 13from crytic_compile.source_unit import SourceUnit 14from crytic_compile.utils.naming import Filename 15 16# Cycle dependency 17if TYPE_CHECKING: 18 from crytic_compile import CryticCompile 19 20# pylint: disable=too-many-instance-attributes 21class CompilationUnit: 22 """CompilationUnit class""" 23 24 def __init__(self, crytic_compile: "CryticCompile", unique_id: str): 25 """Init the object 26 27 Args: 28 crytic_compile (CryticCompile): Associated CryticCompile object 29 unique_id (str): Unique ID used to identify the compilation unit 30 """ 31 32 # mapping from filename to contract name 33 self._filename_to_contracts: Dict[Filename, Set[str]] = defaultdict(set) 34 35 # mapping from filename to source unit 36 self._source_units: Dict[Filename, SourceUnit] = {} 37 38 # set containing all the filenames of this compilation unit 39 self._filenames: List[Filename] = [] 40 41 # mapping from absolute/relative/used to filename 42 self._filenames_lookup: Optional[Dict[str, Filename]] = None 43 44 # compiler.compiler 45 self._compiler_version: CompilerVersion = CompilerVersion( 46 compiler="N/A", version="N/A", optimized=False 47 ) 48 49 # if the compilation unit comes from etherscan-like service and is a proxy, 50 # store the implementation address 51 self._implementation_address: Optional[str] = None 52 53 self._crytic_compile: "CryticCompile" = crytic_compile 54 55 if unique_id == ".": 56 unique_id = str(uuid.uuid4()) 57 58 crytic_compile.compilation_units[unique_id] = self # type: ignore 59 60 self._unique_id = unique_id 61 62 @property 63 def unique_id(self) -> str: 64 """Return the compilation unit ID 65 66 Returns: 67 str: Compilation unit unique ID 68 """ 69 return self._unique_id 70 71 @property 72 def crytic_compile(self) -> "CryticCompile": 73 """Return the CryticCompile object 74 75 Returns: 76 CryticCompile: Associated CryticCompile object 77 """ 78 return self._crytic_compile 79 80 @property 81 def source_units(self) -> Dict[Filename, SourceUnit]: 82 """ 83 Return the dict of the source units 84 85 Returns: 86 Dict[Filename, SourceUnit]: the source units 87 """ 88 return self._source_units 89 90 def source_unit(self, filename: Filename) -> SourceUnit: 91 """ 92 Return the source unit associated to the filename. 93 The source unit must have been created by create_source_units 94 95 Args: 96 filename: filename of the source unit 97 98 Returns: 99 SourceUnit: the source unit 100 """ 101 return self._source_units[filename] 102 103 @property 104 def asts(self) -> Dict[str, Dict]: 105 """ 106 Return all the asts from the compilation unit 107 108 Returns: 109 Dict[str, Dict]: absolute path -> ast 110 """ 111 return { 112 source_unit.filename.absolute: source_unit.ast 113 for source_unit in self.source_units.values() 114 } 115 116 def create_source_unit(self, filename: Filename) -> SourceUnit: 117 """ 118 Create the source unit associated with the filename 119 Add the relevant info in the compilation unit/crytic compile 120 If the source unit already exist, return it 121 Also appends filename to the end of filenames, if not already present 122 So this function should be called in the order you want filenames to have 123 124 Args: 125 filename (Filename): filename of the source unit 126 127 Returns: 128 SourceUnit: the source unit 129 """ 130 if not filename in self._source_units: 131 source_unit = SourceUnit(self, filename) # type: ignore 132 self._source_units[filename] = source_unit 133 if filename not in self.filenames: 134 self.filenames.append(filename) 135 return self._source_units[filename] 136 137 @property 138 def implementation_address(self) -> Optional[str]: 139 """Return the implementation address if the compilation unit is a proxy 140 141 Returns: 142 Optional[str]: Implementation address 143 """ 144 return self._implementation_address 145 146 @implementation_address.setter 147 def implementation_address(self, implementation: str) -> None: 148 """Set the implementation address 149 150 Args: 151 implementation (str): Implementation address 152 """ 153 self._implementation_address = implementation 154 155 # endregion 156 ################################################################################### 157 ################################################################################### 158 # region Filenames 159 ################################################################################### 160 ################################################################################### 161 162 @property 163 def filenames(self) -> List[Filename]: 164 """Return the filenames used by the compilation unit 165 166 Returns: 167 list[Filename]: Filenames used by the compilation units 168 """ 169 return self._filenames 170 171 @filenames.setter 172 def filenames(self, all_filenames: List[Filename]) -> None: 173 """Set the filenames 174 175 Args: 176 all_filenames (List[Filename]): new filenames 177 """ 178 self._filenames = all_filenames 179 180 @property 181 def filename_to_contracts(self) -> Dict[Filename, Set[str]]: 182 """Return a dict mapping the filename to a list of contract declared 183 184 Returns: 185 Dict[Filename, List[str]]: Filename -> List[contract_name] 186 """ 187 return self._filename_to_contracts 188 189 def find_absolute_filename_from_used_filename(self, used_filename: str) -> str: 190 """Return the absolute filename based on the used one 191 192 Args: 193 used_filename (str): Used filename 194 195 Raises: 196 ValueError: If the filename is not found 197 198 Returns: 199 str: Absolute filename 200 """ 201 # Note: we could memoize this function if the third party end up using it heavily 202 # If used_filename is already an absolute path no need to lookup 203 if used_filename in self._crytic_compile.filenames: 204 return used_filename 205 d_file = {f.used: f.absolute for f in self._filenames} 206 if used_filename not in d_file: 207 raise ValueError("f{filename} does not exist in {d}") 208 return d_file[used_filename] 209 210 def relative_filename_from_absolute_filename(self, absolute_filename: str) -> str: 211 """Return the relative file based on the absolute name 212 213 Args: 214 absolute_filename (str): Absolute filename 215 216 Raises: 217 ValueError: If the filename is not found 218 219 Returns: 220 str: Absolute filename 221 """ 222 d_file = {f.absolute: f.relative for f in self._filenames} 223 if absolute_filename not in d_file: 224 raise ValueError("f{absolute_filename} does not exist in {d}") 225 return d_file[absolute_filename] 226 227 def filename_lookup(self, filename: str) -> Filename: 228 """Return a crytic_compile.naming.Filename from a any filename 229 230 Args: 231 filename (str): filename (used/absolute/relative) 232 233 Raises: 234 ValueError: If the filename is not in the project 235 236 Returns: 237 Filename: Associated Filename object 238 """ 239 # pylint: disable=import-outside-toplevel 240 from crytic_compile.platform.truffle import Truffle 241 242 if isinstance(self.crytic_compile.platform, Truffle) and filename.startswith("project:/"): 243 filename = filename[len("project:/") :] 244 245 if self._filenames_lookup is None: 246 self._filenames_lookup = {} 247 for file in self._filenames: 248 self._filenames_lookup[file.absolute] = file 249 self._filenames_lookup[file.relative] = file 250 self._filenames_lookup[file.used] = file 251 if filename not in self._filenames_lookup: 252 raise ValueError( 253 f"{filename} does not exist in {[f.absolute for f in self._filenames_lookup.values()]}" 254 ) 255 return self._filenames_lookup[filename] 256 257 # endregion 258 ################################################################################### 259 ################################################################################### 260 # region Compiler information 261 ################################################################################### 262 ################################################################################### 263 264 @property 265 def compiler_version(self) -> "CompilerVersion": 266 """Return the compiler info 267 268 Returns: 269 CompilerVersion: compiler info 270 """ 271 return self._compiler_version 272 273 @compiler_version.setter 274 def compiler_version(self, compiler: CompilerVersion) -> None: 275 """Set the compiler version 276 277 Args: 278 compiler (CompilerVersion): New compiler version 279 """ 280 self._compiler_version = compiler 281 282 # endregion 283 ################################################################################### 284 ###################################################################################
22class CompilationUnit: 23 """CompilationUnit class""" 24 25 def __init__(self, crytic_compile: "CryticCompile", unique_id: str): 26 """Init the object 27 28 Args: 29 crytic_compile (CryticCompile): Associated CryticCompile object 30 unique_id (str): Unique ID used to identify the compilation unit 31 """ 32 33 # mapping from filename to contract name 34 self._filename_to_contracts: Dict[Filename, Set[str]] = defaultdict(set) 35 36 # mapping from filename to source unit 37 self._source_units: Dict[Filename, SourceUnit] = {} 38 39 # set containing all the filenames of this compilation unit 40 self._filenames: List[Filename] = [] 41 42 # mapping from absolute/relative/used to filename 43 self._filenames_lookup: Optional[Dict[str, Filename]] = None 44 45 # compiler.compiler 46 self._compiler_version: CompilerVersion = CompilerVersion( 47 compiler="N/A", version="N/A", optimized=False 48 ) 49 50 # if the compilation unit comes from etherscan-like service and is a proxy, 51 # store the implementation address 52 self._implementation_address: Optional[str] = None 53 54 self._crytic_compile: "CryticCompile" = crytic_compile 55 56 if unique_id == ".": 57 unique_id = str(uuid.uuid4()) 58 59 crytic_compile.compilation_units[unique_id] = self # type: ignore 60 61 self._unique_id = unique_id 62 63 @property 64 def unique_id(self) -> str: 65 """Return the compilation unit ID 66 67 Returns: 68 str: Compilation unit unique ID 69 """ 70 return self._unique_id 71 72 @property 73 def crytic_compile(self) -> "CryticCompile": 74 """Return the CryticCompile object 75 76 Returns: 77 CryticCompile: Associated CryticCompile object 78 """ 79 return self._crytic_compile 80 81 @property 82 def source_units(self) -> Dict[Filename, SourceUnit]: 83 """ 84 Return the dict of the source units 85 86 Returns: 87 Dict[Filename, SourceUnit]: the source units 88 """ 89 return self._source_units 90 91 def source_unit(self, filename: Filename) -> SourceUnit: 92 """ 93 Return the source unit associated to the filename. 94 The source unit must have been created by create_source_units 95 96 Args: 97 filename: filename of the source unit 98 99 Returns: 100 SourceUnit: the source unit 101 """ 102 return self._source_units[filename] 103 104 @property 105 def asts(self) -> Dict[str, Dict]: 106 """ 107 Return all the asts from the compilation unit 108 109 Returns: 110 Dict[str, Dict]: absolute path -> ast 111 """ 112 return { 113 source_unit.filename.absolute: source_unit.ast 114 for source_unit in self.source_units.values() 115 } 116 117 def create_source_unit(self, filename: Filename) -> SourceUnit: 118 """ 119 Create the source unit associated with the filename 120 Add the relevant info in the compilation unit/crytic compile 121 If the source unit already exist, return it 122 Also appends filename to the end of filenames, if not already present 123 So this function should be called in the order you want filenames to have 124 125 Args: 126 filename (Filename): filename of the source unit 127 128 Returns: 129 SourceUnit: the source unit 130 """ 131 if not filename in self._source_units: 132 source_unit = SourceUnit(self, filename) # type: ignore 133 self._source_units[filename] = source_unit 134 if filename not in self.filenames: 135 self.filenames.append(filename) 136 return self._source_units[filename] 137 138 @property 139 def implementation_address(self) -> Optional[str]: 140 """Return the implementation address if the compilation unit is a proxy 141 142 Returns: 143 Optional[str]: Implementation address 144 """ 145 return self._implementation_address 146 147 @implementation_address.setter 148 def implementation_address(self, implementation: str) -> None: 149 """Set the implementation address 150 151 Args: 152 implementation (str): Implementation address 153 """ 154 self._implementation_address = implementation 155 156 # endregion 157 ################################################################################### 158 ################################################################################### 159 # region Filenames 160 ################################################################################### 161 ################################################################################### 162 163 @property 164 def filenames(self) -> List[Filename]: 165 """Return the filenames used by the compilation unit 166 167 Returns: 168 list[Filename]: Filenames used by the compilation units 169 """ 170 return self._filenames 171 172 @filenames.setter 173 def filenames(self, all_filenames: List[Filename]) -> None: 174 """Set the filenames 175 176 Args: 177 all_filenames (List[Filename]): new filenames 178 """ 179 self._filenames = all_filenames 180 181 @property 182 def filename_to_contracts(self) -> Dict[Filename, Set[str]]: 183 """Return a dict mapping the filename to a list of contract declared 184 185 Returns: 186 Dict[Filename, List[str]]: Filename -> List[contract_name] 187 """ 188 return self._filename_to_contracts 189 190 def find_absolute_filename_from_used_filename(self, used_filename: str) -> str: 191 """Return the absolute filename based on the used one 192 193 Args: 194 used_filename (str): Used filename 195 196 Raises: 197 ValueError: If the filename is not found 198 199 Returns: 200 str: Absolute filename 201 """ 202 # Note: we could memoize this function if the third party end up using it heavily 203 # If used_filename is already an absolute path no need to lookup 204 if used_filename in self._crytic_compile.filenames: 205 return used_filename 206 d_file = {f.used: f.absolute for f in self._filenames} 207 if used_filename not in d_file: 208 raise ValueError("f{filename} does not exist in {d}") 209 return d_file[used_filename] 210 211 def relative_filename_from_absolute_filename(self, absolute_filename: str) -> str: 212 """Return the relative file based on the absolute name 213 214 Args: 215 absolute_filename (str): Absolute filename 216 217 Raises: 218 ValueError: If the filename is not found 219 220 Returns: 221 str: Absolute filename 222 """ 223 d_file = {f.absolute: f.relative for f in self._filenames} 224 if absolute_filename not in d_file: 225 raise ValueError("f{absolute_filename} does not exist in {d}") 226 return d_file[absolute_filename] 227 228 def filename_lookup(self, filename: str) -> Filename: 229 """Return a crytic_compile.naming.Filename from a any filename 230 231 Args: 232 filename (str): filename (used/absolute/relative) 233 234 Raises: 235 ValueError: If the filename is not in the project 236 237 Returns: 238 Filename: Associated Filename object 239 """ 240 # pylint: disable=import-outside-toplevel 241 from crytic_compile.platform.truffle import Truffle 242 243 if isinstance(self.crytic_compile.platform, Truffle) and filename.startswith("project:/"): 244 filename = filename[len("project:/") :] 245 246 if self._filenames_lookup is None: 247 self._filenames_lookup = {} 248 for file in self._filenames: 249 self._filenames_lookup[file.absolute] = file 250 self._filenames_lookup[file.relative] = file 251 self._filenames_lookup[file.used] = file 252 if filename not in self._filenames_lookup: 253 raise ValueError( 254 f"{filename} does not exist in {[f.absolute for f in self._filenames_lookup.values()]}" 255 ) 256 return self._filenames_lookup[filename] 257 258 # endregion 259 ################################################################################### 260 ################################################################################### 261 # region Compiler information 262 ################################################################################### 263 ################################################################################### 264 265 @property 266 def compiler_version(self) -> "CompilerVersion": 267 """Return the compiler info 268 269 Returns: 270 CompilerVersion: compiler info 271 """ 272 return self._compiler_version 273 274 @compiler_version.setter 275 def compiler_version(self, compiler: CompilerVersion) -> None: 276 """Set the compiler version 277 278 Args: 279 compiler (CompilerVersion): New compiler version 280 """ 281 self._compiler_version = compiler 282 283 # endregion 284 ################################################################################### 285 ###################################################################################
CompilationUnit class
25 def __init__(self, crytic_compile: "CryticCompile", unique_id: str): 26 """Init the object 27 28 Args: 29 crytic_compile (CryticCompile): Associated CryticCompile object 30 unique_id (str): Unique ID used to identify the compilation unit 31 """ 32 33 # mapping from filename to contract name 34 self._filename_to_contracts: Dict[Filename, Set[str]] = defaultdict(set) 35 36 # mapping from filename to source unit 37 self._source_units: Dict[Filename, SourceUnit] = {} 38 39 # set containing all the filenames of this compilation unit 40 self._filenames: List[Filename] = [] 41 42 # mapping from absolute/relative/used to filename 43 self._filenames_lookup: Optional[Dict[str, Filename]] = None 44 45 # compiler.compiler 46 self._compiler_version: CompilerVersion = CompilerVersion( 47 compiler="N/A", version="N/A", optimized=False 48 ) 49 50 # if the compilation unit comes from etherscan-like service and is a proxy, 51 # store the implementation address 52 self._implementation_address: Optional[str] = None 53 54 self._crytic_compile: "CryticCompile" = crytic_compile 55 56 if unique_id == ".": 57 unique_id = str(uuid.uuid4()) 58 59 crytic_compile.compilation_units[unique_id] = self # type: ignore 60 61 self._unique_id = unique_id
Init the object
Args: crytic_compile (CryticCompile): Associated CryticCompile object unique_id (str): Unique ID used to identify the compilation unit
63 @property 64 def unique_id(self) -> str: 65 """Return the compilation unit ID 66 67 Returns: 68 str: Compilation unit unique ID 69 """ 70 return self._unique_id
Return the compilation unit ID
Returns: str: Compilation unit unique ID
72 @property 73 def crytic_compile(self) -> "CryticCompile": 74 """Return the CryticCompile object 75 76 Returns: 77 CryticCompile: Associated CryticCompile object 78 """ 79 return self._crytic_compile
Return the CryticCompile object
Returns: CryticCompile: Associated CryticCompile object
81 @property 82 def source_units(self) -> Dict[Filename, SourceUnit]: 83 """ 84 Return the dict of the source units 85 86 Returns: 87 Dict[Filename, SourceUnit]: the source units 88 """ 89 return self._source_units
Return the dict of the source units
Returns: Dict[Filename, SourceUnit]: the source units
91 def source_unit(self, filename: Filename) -> SourceUnit: 92 """ 93 Return the source unit associated to the filename. 94 The source unit must have been created by create_source_units 95 96 Args: 97 filename: filename of the source unit 98 99 Returns: 100 SourceUnit: the source unit 101 """ 102 return self._source_units[filename]
Return the source unit associated to the filename. The source unit must have been created by create_source_units
Args: filename: filename of the source unit
Returns: SourceUnit: the source unit
104 @property 105 def asts(self) -> Dict[str, Dict]: 106 """ 107 Return all the asts from the compilation unit 108 109 Returns: 110 Dict[str, Dict]: absolute path -> ast 111 """ 112 return { 113 source_unit.filename.absolute: source_unit.ast 114 for source_unit in self.source_units.values() 115 }
Return all the asts from the compilation unit
Returns: Dict[str, Dict]: absolute path -> ast
117 def create_source_unit(self, filename: Filename) -> SourceUnit: 118 """ 119 Create the source unit associated with the filename 120 Add the relevant info in the compilation unit/crytic compile 121 If the source unit already exist, return it 122 Also appends filename to the end of filenames, if not already present 123 So this function should be called in the order you want filenames to have 124 125 Args: 126 filename (Filename): filename of the source unit 127 128 Returns: 129 SourceUnit: the source unit 130 """ 131 if not filename in self._source_units: 132 source_unit = SourceUnit(self, filename) # type: ignore 133 self._source_units[filename] = source_unit 134 if filename not in self.filenames: 135 self.filenames.append(filename) 136 return self._source_units[filename]
Create the source unit associated with the filename Add the relevant info in the compilation unit/crytic compile If the source unit already exist, return it Also appends filename to the end of filenames, if not already present So this function should be called in the order you want filenames to have
Args: filename (Filename): filename of the source unit
Returns: SourceUnit: the source unit
138 @property 139 def implementation_address(self) -> Optional[str]: 140 """Return the implementation address if the compilation unit is a proxy 141 142 Returns: 143 Optional[str]: Implementation address 144 """ 145 return self._implementation_address
Return the implementation address if the compilation unit is a proxy
Returns: Optional[str]: Implementation address
163 @property 164 def filenames(self) -> List[Filename]: 165 """Return the filenames used by the compilation unit 166 167 Returns: 168 list[Filename]: Filenames used by the compilation units 169 """ 170 return self._filenames
Return the filenames used by the compilation unit
Returns: list[Filename]: Filenames used by the compilation units
181 @property 182 def filename_to_contracts(self) -> Dict[Filename, Set[str]]: 183 """Return a dict mapping the filename to a list of contract declared 184 185 Returns: 186 Dict[Filename, List[str]]: Filename -> List[contract_name] 187 """ 188 return self._filename_to_contracts
Return a dict mapping the filename to a list of contract declared
Returns: Dict[Filename, List[str]]: Filename -> List[contract_name]
190 def find_absolute_filename_from_used_filename(self, used_filename: str) -> str: 191 """Return the absolute filename based on the used one 192 193 Args: 194 used_filename (str): Used filename 195 196 Raises: 197 ValueError: If the filename is not found 198 199 Returns: 200 str: Absolute filename 201 """ 202 # Note: we could memoize this function if the third party end up using it heavily 203 # If used_filename is already an absolute path no need to lookup 204 if used_filename in self._crytic_compile.filenames: 205 return used_filename 206 d_file = {f.used: f.absolute for f in self._filenames} 207 if used_filename not in d_file: 208 raise ValueError("f{filename} does not exist in {d}") 209 return d_file[used_filename]
Return the absolute filename based on the used one
Args: used_filename (str): Used filename
Raises: ValueError: If the filename is not found
Returns: str: Absolute filename
211 def relative_filename_from_absolute_filename(self, absolute_filename: str) -> str: 212 """Return the relative file based on the absolute name 213 214 Args: 215 absolute_filename (str): Absolute filename 216 217 Raises: 218 ValueError: If the filename is not found 219 220 Returns: 221 str: Absolute filename 222 """ 223 d_file = {f.absolute: f.relative for f in self._filenames} 224 if absolute_filename not in d_file: 225 raise ValueError("f{absolute_filename} does not exist in {d}") 226 return d_file[absolute_filename]
Return the relative file based on the absolute name
Args: absolute_filename (str): Absolute filename
Raises: ValueError: If the filename is not found
Returns: str: Absolute filename
228 def filename_lookup(self, filename: str) -> Filename: 229 """Return a crytic_compile.naming.Filename from a any filename 230 231 Args: 232 filename (str): filename (used/absolute/relative) 233 234 Raises: 235 ValueError: If the filename is not in the project 236 237 Returns: 238 Filename: Associated Filename object 239 """ 240 # pylint: disable=import-outside-toplevel 241 from crytic_compile.platform.truffle import Truffle 242 243 if isinstance(self.crytic_compile.platform, Truffle) and filename.startswith("project:/"): 244 filename = filename[len("project:/") :] 245 246 if self._filenames_lookup is None: 247 self._filenames_lookup = {} 248 for file in self._filenames: 249 self._filenames_lookup[file.absolute] = file 250 self._filenames_lookup[file.relative] = file 251 self._filenames_lookup[file.used] = file 252 if filename not in self._filenames_lookup: 253 raise ValueError( 254 f"{filename} does not exist in {[f.absolute for f in self._filenames_lookup.values()]}" 255 ) 256 return self._filenames_lookup[filename]
Return a crytic_compile.naming.Filename from a any filename
Args: filename (str): filename (used/absolute/relative)
Raises: ValueError: If the filename is not in the project
Returns: Filename: Associated Filename object
265 @property 266 def compiler_version(self) -> "CompilerVersion": 267 """Return the compiler info 268 269 Returns: 270 CompilerVersion: compiler info 271 """ 272 return self._compiler_version
Return the compiler info
Returns: CompilerVersion: compiler info