Coverage for pyTooling/CallGraph/__init__.py: 100%

219 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-12 14:08 +0000

1# ==================================================================================================================== # 

2# _____ _ _ ____ _ _ ____ _ # 

3# _ __ _ |_ _|__ ___ | (_)_ __ __ _ / ___|__ _| | |/ ___|_ __ __ _ _ __ | |__ # 

4# | '_ \| | | || |/ _ \ / _ \| | | '_ \ / _` || | / _` | | | | _| '__/ _` | '_ \| '_ \ # 

5# | |_) | |_| || | (_) | (_) | | | | | | (_| || |__| (_| | | | |_| | | | (_| | |_) | | | | # 

6# | .__/ \__, ||_|\___/ \___/|_|_|_| |_|\__, (_)____\__,_|_|_|\____|_| \__,_| .__/|_| |_| # 

7# |_| |___/ |___/ |_| # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Patrick Lehmann # 

11# # 

12# License: # 

13# ==================================================================================================================== # 

14# Copyright 2026-2026 Patrick Lehmann - Bötzingen, Germany # 

15# # 

16# Licensed under the Apache License, Version 2.0 (the "License"); # 

17# you may not use this file except in compliance with the License. # 

18# You may obtain a copy of the License at # 

19# # 

20# http://www.apache.org/licenses/LICENSE-2.0 # 

21# # 

22# Unless required by applicable law or agreed to in writing, software # 

23# distributed under the License is distributed on an "AS IS" BASIS, # 

24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

25# See the License for the specific language governing permissions and # 

26# limitations under the License. # 

27# # 

28# SPDX-License-Identifier: Apache-2.0 # 

29# ==================================================================================================================== # 

30# 

31""" 

32A data structure to describe the **call graph** of a program: its functions and the calls between them. 

33 

34A :class:`CallGraph` describes the functions of e.g. a program, a translation unit or a profiled run. A 

35:class:`Function` is identified by an ID unique within its graph, e.g. a qualified or a mangled name. A :class:`Call` 

36leads from the calling function to the called function, so a recursion is a cycle of the graph. A :class:`CallKind` 

37says how the callee was determined, and a :class:`FunctionKind` if a function is defined in the described code, defined 

38elsewhere, or a placeholder for unknown functions, e.g. the targets of an indirect call. 

39 

40Functions and calls carry an optional execution count, e.g. from a profiler, and an optional value of any type. A 

41function can refer to its control flow graph, and a call to its call site: the basic block of the caller's control flow 

42graph the call is made from. Call graphs, functions and calls carry key-value-pairs, e.g. the results of an analysis. 

43:meth:`CallGraph.ToGraph` converts a call graph into a :class:`~pyTooling.Graph.Graph`. 

44 

45.. hint:: 

46 

47 See :ref:`high-level help <STRUCT/CallGraph>` for explanations and usage examples. 

48 

49.. seealso:: 

50 

51 :mod:`pyTooling.ControlFlow` 

52 |rarr| The control flow graph of a function, whose blocks are the call sites. 

53 :mod:`pyTooling.Graph` 

54 |rarr| The graph data structure a call graph is converted into. 

55 :mod:`pyTooling.Graph.GraphML` 

56 |rarr| Writing a graph as a GraphML document. 

57""" 

58from __future__ import annotations 

59 

60from enum import Enum 

61from typing import Any, Callable, Generator, Hashable, Mapping, Optional as Nullable 

62 

63from pyTooling.Common import getFullyQualifiedName 

64from pyTooling.ControlFlow import Base, BaseWithCountAndValue, BasicBlock, BlockKind, ControlFlowGraph 

65from pyTooling.Decorators import export, readonly 

66from pyTooling.Exceptions import ToolingException 

67from pyTooling.Graph import Graph, Vertex 

68from pyTooling.MetaClasses import ExtendedType 

69 

70 

71# A class with a property named like a class - ``ControlFlowGraph`` - can't name that class in the annotation of a 

72# field: the class body's namespace, where annotations are evaluated, binds the name to the property. 

73_ControlFlowGraph = ControlFlowGraph 

74 

75 

76@export 

77class FunctionKind(Enum): 

78 """ 

79 Kind of a function: whether the call graph describes its code. 

80 """ 

81 Defined = 0 #: The function is defined in the described code, e.g. in the translation unit or the profiled program. 

82 External = 1 #: The function is defined elsewhere, e.g. a library function or a Python built-in function. 

83 Unknown = 2 #: A placeholder for functions the source doesn't know, e.g. the targets of an unresolved indirect call. 

84 

85 def __str__(self) -> str: 

86 """ 

87 Return the member's name. 

88 

89 :returns: Name of the member, e.g. ``External``. 

90 """ 

91 return self.name 

92 

93 

94@export 

95class CallKind(Enum): 

96 """ 

97 Kind of a call: how the callee is determined. 

98 """ 

99 Default = 0 #: The call wasn't classified, e.g. a pair of caller and callee a profiler reports. 

100 Direct = 1 #: The call names its callee, e.g. a C function called by name. 

101 Indirect = 2 #: The callee is determined at run time, e.g. through a function pointer or a virtual method. 

102 

103 def __str__(self) -> str: 

104 """ 

105 Return the member's name. 

106 

107 :returns: Name of the member, e.g. ``Indirect``. 

108 """ 

109 return self.name 

110 

111 

112@export 

113class CallGraphError(ToolingException): 

114 """Base-exception of all exceptions raised by :mod:`pyTooling.CallGraph`.""" 

115 

116 

117@export 

118class DuplicateFunctionError(CallGraphError): 

119 """The exception raised for a function with an ID, which the graph has already.""" 

120 

121 

122@export 

123class Function(BaseWithCountAndValue): 

124 """ 

125 A function of a call graph. 

126 

127 A function is identified by an ID unique within its graph, e.g. a qualified or a mangled name. Its outbound calls lead 

128 to the functions it calls, its inbound calls come from the functions calling it. It can refer to its control flow 

129 graph, whose blocks are the call sites of its outbound calls. 

130 """ 

131 _callGraph: _CallGraph #: The call graph this function belongs to. 

132 _id: Hashable #: ID of the function, unique within its graph. 

133 _kind: FunctionKind #: Kind of the function. 

134 _controlFlowGraph: Nullable[_ControlFlowGraph] #: The function's control flow graph, if it's known. 

135 _inboundCalls: list[Call] #: Calls of this function. 

136 _outboundCalls: list[Call] #: Calls this function makes. 

137 

138 def __init__( 

139 self, 

140 callGraph: _CallGraph, 

141 functionID: Hashable, 

142 *, 

143 kind: FunctionKind = FunctionKind.Defined, 

144 controlFlowGraph: Nullable[_ControlFlowGraph] = None, 

145 count: Nullable[int] = None, 

146 value: Nullable[Any] = None, 

147 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None 

148 ) -> None: 

149 """ 

150 Initializes a function and adds it to its graph. 

151 

152 :param callGraph: The call graph the function belongs to. 

153 :param functionID: ID of the function, unique within its graph. 

154 :param kind: Optional, kind of the function. Default: :attr:`FunctionKind.Defined`. 

155 :param controlFlowGraph: Optional, the function's control flow graph. Default: ``None``. 

156 :param count: Optional, how often the function was called. Default: ``None``. 

157 :param value: Optional, any value attached to the function, e.g. its location. Default: ``None``. 

158 :param keyValuePairs: Optional, mapping (dictionary) of key-value-pairs. Default: ``None``. 

159 :raises TypeError: If parameter 'count' is not of type :class:`int`. 

160 :raises ValueError: If parameter 'count' is negative. 

161 :raises ValueError: If parameter 'callGraph' is None. 

162 :raises TypeError: If parameter 'callGraph' is not of type :class:`CallGraph`. 

163 :raises ValueError: If parameter 'functionID' is None. 

164 :raises TypeError: If parameter 'functionID' is not hashable. 

165 :raises DuplicateFunctionError: If the graph has a function with that ID already. 

166 :raises ValueError: If parameter 'kind' is None. 

167 :raises TypeError: If parameter 'kind' is not of type :class:`FunctionKind`. 

168 :raises TypeError: If parameter 'controlFlowGraph' is not of type 

169 :class:`~pyTooling.ControlFlow.ControlFlowGraph`. 

170 :raises ValueError: If parameter 'controlFlowGraph' is given for a function of kind 

171 :attr:`FunctionKind.Unknown`. 

172 """ 

173 super().__init__(count, value, keyValuePairs) 

174 

175 if callGraph is None: 

176 raise ValueError("Parameter 'callGraph' is None.") 

177 elif not isinstance(callGraph, _CallGraph): 

178 ex = TypeError("Parameter 'callGraph' is not of type 'CallGraph'.") 

179 ex.add_note(f"Got type '{getFullyQualifiedName(callGraph)}'.") 

180 raise ex 

181 

182 if functionID is None: 

183 raise ValueError("Parameter 'functionID' is None.") 

184 elif not isinstance(functionID, Hashable): 

185 ex = TypeError("Parameter 'functionID' is not hashable.") 

186 ex.add_note(f"Got type '{getFullyQualifiedName(functionID)}'.") 

187 raise ex 

188 elif functionID in callGraph._functions: 

189 raise DuplicateFunctionError(f"Call graph '{callGraph._name}' has a function '{functionID}' already.") 

190 

191 if kind is None: 

192 raise ValueError("Parameter 'kind' is None.") 

193 elif not isinstance(kind, FunctionKind): 

194 ex = TypeError("Parameter 'kind' is not of type 'FunctionKind'.") 

195 ex.add_note(f"Got type '{getFullyQualifiedName(kind)}'.") 

196 raise ex 

197 

198 if controlFlowGraph is not None: 

199 if not isinstance(controlFlowGraph, _ControlFlowGraph): 

200 ex = TypeError("Parameter 'controlFlowGraph' is not of type 'ControlFlowGraph'.") 

201 ex.add_note(f"Got type '{getFullyQualifiedName(controlFlowGraph)}'.") 

202 raise ex 

203 elif kind is FunctionKind.Unknown: 

204 ex = ValueError("Parameter 'controlFlowGraph' is given for a function of kind 'Unknown'.") 

205 ex.add_note(f"Got function '{functionID}'.") 

206 raise ex 

207 

208 self._callGraph = callGraph 

209 self._id = functionID 

210 self._kind = kind 

211 self._controlFlowGraph = controlFlowGraph 

212 self._inboundCalls = [] 

213 self._outboundCalls = [] 

214 

215 callGraph._functions[functionID] = self 

216 

217 @readonly 

218 def CallGraph(self) -> _CallGraph: 

219 """ 

220 Read-only property to access the call graph this function belongs to (:attr:`_callGraph`). 

221 

222 :returns: The call graph. 

223 """ 

224 return self._callGraph 

225 

226 @readonly 

227 def ID(self) -> Hashable: 

228 """ 

229 Read-only property to access the function's ID (:attr:`_id`). 

230 

231 :returns: ID of the function, unique within its graph. 

232 """ 

233 return self._id 

234 

235 @readonly 

236 def Kind(self) -> FunctionKind: 

237 """ 

238 Read-only property to access the function's kind (:attr:`_kind`). 

239 

240 :returns: Kind of the function. 

241 """ 

242 return self._kind 

243 

244 @readonly 

245 def ControlFlowGraph(self) -> Nullable[_ControlFlowGraph]: 

246 """ 

247 Read-only property to access the function's control flow graph (:attr:`_controlFlowGraph`). 

248 

249 :returns: The control flow graph, or ``None`` if it's unknown. 

250 """ 

251 return self._controlFlowGraph 

252 

253 @readonly 

254 def InboundCalls(self) -> tuple[Call, ...]: 

255 """ 

256 Read-only property to return the calls of this function. 

257 

258 :returns: The inbound calls, in the order they were created. 

259 """ 

260 return tuple(self._inboundCalls) 

261 

262 @readonly 

263 def OutboundCalls(self) -> tuple[Call, ...]: 

264 """ 

265 Read-only property to return the calls this function makes. 

266 

267 :returns: The outbound calls, in the order they were created. 

268 """ 

269 return tuple(self._outboundCalls) 

270 

271 def CallTo( 

272 self, 

273 callee: Function, 

274 callCount: Nullable[int] = None, 

275 callValue: Nullable[Any] = None, 

276 callKind: CallKind = CallKind.Default, 

277 callSite: Nullable[BasicBlock] = None, 

278 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None 

279 ) -> Call: 

280 """ 

281 Create an outbound call from this function to the called function. 

282 

283 :param callee: The called function. 

284 :param callCount: Optional, how often the call was made. Default: ``None``. 

285 :param callValue: Optional, any value attached to the call, e.g. the call's source line. Default: ``None``. 

286 :param callKind: Optional, kind of the call. Default: :attr:`CallKind.Default`. 

287 :param callSite: Optional, the block of the caller's control flow graph the call is made from. 

288 Default: ``None``. 

289 :param keyValuePairs: Optional, mapping (dictionary) of key-value-pairs for the call. Default: ``None``. 

290 :returns: The call from this function to the callee. 

291 :raises ValueError: If parameter 'callee' is None. 

292 :raises TypeError: If parameter 'callee' is not of type :class:`Function`. 

293 :raises ValueError: If parameter 'callee' belongs to another graph than this function. 

294 :raises TypeError: If parameter 'callCount' is not of type :class:`int`. 

295 :raises ValueError: If parameter 'callCount' is negative. 

296 :raises ValueError: If parameter 'callKind' is None. 

297 :raises TypeError: If parameter 'callKind' is not of type :class:`CallKind`. 

298 :raises TypeError: If parameter 'callSite' is not of type :class:`~pyTooling.ControlFlow.BasicBlock`. 

299 :raises ValueError: If parameter 'callSite' is given, but the caller has no control flow graph. 

300 :raises ValueError: If parameter 'callSite' belongs to another control flow graph than the caller's. 

301 :raises ValueError: If parameter 'callSite' is not a block of :attr:`~pyTooling.ControlFlow.BlockKind.Code`. 

302 

303 .. seealso:: 

304 

305 :meth:`CallFrom` 

306 |rarr| Create an inbound call from a calling function to this function. 

307 """ 

308 return Call( 

309 self, callee, callKind, callSite=callSite, count=callCount, value=callValue, keyValuePairs=keyValuePairs 

310 ) 

311 

312 def CallFrom( 

313 self, 

314 caller: Function, 

315 callCount: Nullable[int] = None, 

316 callValue: Nullable[Any] = None, 

317 callKind: CallKind = CallKind.Default, 

318 callSite: Nullable[BasicBlock] = None, 

319 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None 

320 ) -> Call: 

321 """ 

322 Create an inbound call from the calling function to this function. 

323 

324 :param caller: The calling function. 

325 :param callCount: Optional, how often the call was made. Default: ``None``. 

326 :param callValue: Optional, any value attached to the call, e.g. the call's source line. Default: ``None``. 

327 :param callKind: Optional, kind of the call. Default: :attr:`CallKind.Default`. 

328 :param callSite: Optional, the block of the caller's control flow graph the call is made from. 

329 Default: ``None``. 

330 :param keyValuePairs: Optional, mapping (dictionary) of key-value-pairs for the call. Default: ``None``. 

331 :returns: The call from the caller to this function. 

332 :raises ValueError: If parameter 'caller' is None. 

333 :raises TypeError: If parameter 'caller' is not of type :class:`Function`. 

334 :raises ValueError: If this function belongs to another graph than parameter 'caller'. 

335 :raises TypeError: If parameter 'callCount' is not of type :class:`int`. 

336 :raises ValueError: If parameter 'callCount' is negative. 

337 :raises ValueError: If parameter 'callKind' is None. 

338 :raises TypeError: If parameter 'callKind' is not of type :class:`CallKind`. 

339 :raises TypeError: If parameter 'callSite' is not of type :class:`~pyTooling.ControlFlow.BasicBlock`. 

340 :raises ValueError: If parameter 'callSite' is given, but the caller has no control flow graph. 

341 :raises ValueError: If parameter 'callSite' belongs to another control flow graph than the caller's. 

342 :raises ValueError: If parameter 'callSite' is not a block of :attr:`~pyTooling.ControlFlow.BlockKind.Code`. 

343 

344 .. seealso:: 

345 

346 :meth:`CallTo` 

347 |rarr| Create an outbound call from this function to a called function. 

348 """ 

349 return Call( 

350 caller, self, callKind, callSite=callSite, count=callCount, value=callValue, keyValuePairs=keyValuePairs 

351 ) 

352 

353 

354@export 

355class Call(BaseWithCountAndValue): 

356 """ 

357 A call in a call graph, leading from the calling function to the called function. 

358 

359 The call's :class:`CallKind` says how the callee is determined. A function calling itself is a call from and to the 

360 same function. Two functions can be connected by several calls, e.g. one per call site. 

361 """ 

362 _caller: Function #: The calling function. 

363 _callee: Function #: The called function. 

364 _kind: CallKind #: Kind of the call. 

365 _callSite: Nullable[BasicBlock] #: The block of the caller's control flow graph the call is made from, if it's known. 

366 

367 def __init__( 

368 self, 

369 caller: Function, 

370 callee: Function, 

371 kind: CallKind = CallKind.Default, 

372 *, 

373 callSite: Nullable[BasicBlock] = None, 

374 count: Nullable[int] = None, 

375 value: Nullable[Any] = None, 

376 keyValuePairs: Nullable[Mapping[Hashable, Any]] = None 

377 ) -> None: 

378 """ 

379 Initializes a call between two functions of a graph and adds it to the graph. 

380 

381 :param caller: The calling function. 

382 :param callee: The called function. 

383 :param kind: Optional, kind of the call. Default: :attr:`CallKind.Default`. 

384 :param callSite: Optional, the block of the caller's control flow graph the call is made from. 

385 Default: ``None``. 

386 :param count: Optional, how often the call was made. Default: ``None``. 

387 :param value: Optional, any value attached to the call, e.g. the source line of the call. Default: ``None``. 

388 :param keyValuePairs: Optional, mapping (dictionary) of key-value-pairs. Default: ``None``. 

389 :raises TypeError: If parameter 'count' is not of type :class:`int`. 

390 :raises ValueError: If parameter 'count' is negative. 

391 :raises ValueError: If parameter 'caller' is None. 

392 :raises TypeError: If parameter 'caller' is not of type :class:`Function`. 

393 :raises ValueError: If parameter 'callee' is None. 

394 :raises TypeError: If parameter 'callee' is not of type :class:`Function`. 

395 :raises ValueError: If parameter 'callee' belongs to another graph than parameter 'caller'. 

396 :raises ValueError: If parameter 'kind' is None. 

397 :raises TypeError: If parameter 'kind' is not of type :class:`CallKind`. 

398 :raises TypeError: If parameter 'callSite' is not of type :class:`~pyTooling.ControlFlow.BasicBlock`. 

399 :raises ValueError: If parameter 'callSite' is given, but the caller has no control flow graph. 

400 :raises ValueError: If parameter 'callSite' belongs to another control flow graph than the caller's. 

401 :raises ValueError: If parameter 'callSite' is not a block of :attr:`~pyTooling.ControlFlow.BlockKind.Code`. 

402 """ 

403 super().__init__(count, value, keyValuePairs) 

404 

405 if caller is None: 

406 raise ValueError("Parameter 'caller' is None.") 

407 elif not isinstance(caller, Function): 

408 ex = TypeError("Parameter 'caller' is not of type 'Function'.") 

409 ex.add_note(f"Got type '{getFullyQualifiedName(caller)}'.") 

410 raise ex 

411 

412 if callee is None: 

413 raise ValueError("Parameter 'callee' is None.") 

414 elif not isinstance(callee, Function): 

415 ex = TypeError("Parameter 'callee' is not of type 'Function'.") 

416 ex.add_note(f"Got type '{getFullyQualifiedName(callee)}'.") 

417 raise ex 

418 elif callee._callGraph is not caller._callGraph: 

419 ex = ValueError("Parameter 'callee' belongs to another graph than parameter 'caller'.") 

420 ex.add_note(f"Got graphs '{caller._callGraph._name}' and '{callee._callGraph._name}'.") 

421 raise ex 

422 

423 if kind is None: 

424 raise ValueError("Parameter 'kind' is None.") 

425 elif not isinstance(kind, CallKind): 

426 ex = TypeError("Parameter 'kind' is not of type 'CallKind'.") 

427 ex.add_note(f"Got type '{getFullyQualifiedName(kind)}'.") 

428 raise ex 

429 

430 if callSite is not None: 

431 if not isinstance(callSite, BasicBlock): 

432 ex = TypeError("Parameter 'callSite' is not of type 'BasicBlock'.") 

433 ex.add_note(f"Got type '{getFullyQualifiedName(callSite)}'.") 

434 raise ex 

435 elif caller._controlFlowGraph is None: 

436 ex = ValueError("Parameter 'callSite' is given, but the caller has no control flow graph.") 

437 ex.add_note(f"Got caller '{caller._id}'.") 

438 raise ex 

439 elif callSite._controlFlowGraph is not caller._controlFlowGraph: 

440 ex = ValueError("Parameter 'callSite' belongs to another control flow graph than the caller's.") 

441 ex.add_note(f"Got graphs '{callSite._controlFlowGraph._name}' and '{caller._controlFlowGraph._name}'.") 

442 raise ex 

443 elif callSite._kind is not BlockKind.Code: 

444 ex = ValueError("Parameter 'callSite' is not a block of code.") 

445 ex.add_note(f"Got block '{callSite._id}' of kind '{callSite._kind}'.") 

446 raise ex 

447 

448 self._caller = caller 

449 self._callee = callee 

450 self._kind = kind 

451 self._callSite = callSite 

452 

453 caller._outboundCalls.append(self) 

454 callee._inboundCalls.append(self) 

455 caller._callGraph._calls.append(self) 

456 

457 @readonly 

458 def Caller(self) -> Function: 

459 """ 

460 Read-only property to access the calling function (:attr:`_caller`). 

461 

462 :returns: The caller. 

463 """ 

464 return self._caller 

465 

466 @readonly 

467 def Callee(self) -> Function: 

468 """ 

469 Read-only property to access the called function (:attr:`_callee`). 

470 

471 :returns: The callee. 

472 """ 

473 return self._callee 

474 

475 @readonly 

476 def Kind(self) -> CallKind: 

477 """ 

478 Read-only property to access the call's kind (:attr:`_kind`). 

479 

480 :returns: Kind of the call. 

481 """ 

482 return self._kind 

483 

484 @readonly 

485 def CallSite(self) -> Nullable[BasicBlock]: 

486 """ 

487 Read-only property to access the block of the caller's control flow graph the call is made from (:attr:`_callSite`). 

488 

489 :returns: The call site, or ``None`` if it's unknown. 

490 """ 

491 return self._callSite 

492 

493 

494@export 

495class CallGraph(Base): 

496 """ 

497 The call graph of a program: its functions and the calls between them. 

498 

499 A function is looked up by its ID: :pycode:`graph.GetFunction("main")` returns the function with ID ``main``, and 

500 :pycode:`graph.ContainsFunction("main")` checks if the graph has one. :meth:`IterateFunctions` yields the functions in 

501 the order they were created. The graph's dunder methods serve its key-value-pairs, like a function's or a call's. 

502 """ 

503 _name: str #: Name of the graph, e.g. of the program or the translation unit. 

504 _functions: dict[Hashable, Function] #: Functions of the graph by ID, in the order they were created. 

505 _calls: list[Call] #: Calls of the graph, in the order they were created. 

506 

507 def __init__(self, name: str, *, keyValuePairs: Nullable[Mapping[Hashable, Any]] = None) -> None: 

508 """ 

509 Initializes an empty call graph. 

510 

511 :param name: Name of the graph, e.g. of the program or the translation unit. 

512 :param keyValuePairs: Optional, mapping (dictionary) of key-value-pairs. Default: ``None``. 

513 :raises ValueError: If parameter 'name' is None. 

514 :raises TypeError: If parameter 'name' is not of type :class:`str`. 

515 :raises ValueError: If parameter 'name' is empty. 

516 """ 

517 super().__init__(keyValuePairs) 

518 

519 if name is None: 

520 raise ValueError("Parameter 'name' is None.") 

521 elif not isinstance(name, str): 

522 ex = TypeError("Parameter 'name' is not of type 'str'.") 

523 ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") 

524 raise ex 

525 elif name == "": 

526 raise ValueError("Parameter 'name' is empty.") 

527 

528 self._name = name 

529 self._functions = {} 

530 self._calls = [] 

531 

532 @readonly 

533 def Name(self) -> str: 

534 """ 

535 Read-only property to access the name of the graph (:attr:`_name`). 

536 

537 :returns: Name of the graph. 

538 """ 

539 return self._name 

540 

541 @readonly 

542 def Functions(self) -> dict[Hashable, Function]: 

543 """ 

544 Read-only property to access the functions of the graph by ID (:attr:`_functions`). 

545 

546 :returns: The functions, in the order they were created. 

547 """ 

548 return self._functions 

549 

550 @readonly 

551 def Calls(self) -> list[Call]: 

552 """ 

553 Read-only property to access the calls of the graph (:attr:`_calls`). 

554 

555 :returns: The calls, in the order they were created. 

556 """ 

557 return self._calls 

558 

559 @readonly 

560 def FunctionCount(self) -> int: 

561 """ 

562 Read-only property to return the number of functions in the graph. 

563 

564 :returns: Number of functions. 

565 """ 

566 return len(self._functions) 

567 

568 def ContainsFunction(self, functionID: Hashable) -> bool: 

569 """ 

570 Check if the graph has a function with the given ID. 

571 

572 :param functionID: ID of the function. 

573 :returns: ``True``, if the graph has a function with that ID. 

574 """ 

575 return functionID in self._functions 

576 

577 def GetFunction(self, functionID: Hashable) -> Function: 

578 """ 

579 Return the function with the given ID. 

580 

581 :param functionID: ID of the function. 

582 :returns: The function with that ID. 

583 :raises KeyError: If the graph has no function with that ID. 

584 """ 

585 return self._functions[functionID] 

586 

587 def IterateFunctions( 

588 self, 

589 predicate: Nullable[Callable[[Function], bool]] = None 

590 ) -> Generator[Function, None, None]: 

591 """ 

592 Iterate all or selected functions of the graph. 

593 

594 If parameter ``predicate`` is not None, the given filter function is used to skip functions in the generator. 

595 

596 :param predicate: Optional, filter function accepting any function and returning a boolean. 

597 :returns: A generator to iterate the functions, in the order they were created. 

598 """ 

599 if predicate is None: 

600 yield from self._functions.values() 

601 else: 

602 for function in self._functions.values(): 

603 if predicate(function): 

604 yield function 

605 

606 def ToGraph(self) -> Graph: 

607 """ 

608 Convert the call graph into a :class:`~pyTooling.Graph.Graph`. 

609 

610 Every function becomes a :class:`~pyTooling.Graph.Vertex` with the function's ID as 

611 :attr:`~pyTooling.Graph.Vertex.ID` and the function's value as :attr:`~pyTooling.Graph.Vertex.Value`, so 

612 :meth:`Graph.GetVertexByID <pyTooling.Graph.Graph.GetVertexByID>` finds a function's vertex. Every call becomes an 

613 :class:`~pyTooling.Graph.Edge` from the caller to the callee, with the call's kind as 

614 :attr:`~pyTooling.Graph.BaseEdge.Kind` and the call's value as value. 

615 

616 The graph, its vertices and its edges carry a copy of the key-value-pairs of the call graph, its functions and its 

617 calls. Vertices and edges carry these key-value-pairs more, which a writer like :mod:`pyTooling.Graph.GraphML` 

618 writes out: 

619 

620 * ``kind``: the function's :class:`FunctionKind` or the call's :class:`CallKind` member, whose :func:`str` is its 

621 name. 

622 * ``count``: the execution count; only if it's known. 

623 * ``callsite``: on an edge, the ID of the call site's block; only if the call site is known. 

624 

625 :returns: The graph, named like the call graph. 

626 """ 

627 graph = Graph(name=self._name, keyValuePairs=self._dict) 

628 

629 vertices: dict[Hashable, Vertex] = {} 

630 for function in self._functions.values(): 

631 keyValuePairs: dict[Hashable, Any] = {**function._dict, "kind": function._kind} 

632 if function._count is not None: 

633 keyValuePairs["count"] = function._count 

634 

635 vertices[function._id] = Vertex( 

636 vertexID=function._id, 

637 value=function._value, 

638 keyValuePairs=keyValuePairs, 

639 graph=graph 

640 ) 

641 

642 for call in self._calls: 

643 keyValuePairs = {**call._dict, "kind": call._kind} 

644 if call._count is not None: 

645 keyValuePairs["count"] = call._count 

646 

647 if call._callSite is not None: 

648 keyValuePairs["callsite"] = call._callSite._id 

649 

650 vertices[call._caller._id].EdgeToVertex( 

651 vertices[call._callee._id], 

652 edgeValue=call._value, 

653 edgeKind=call._kind, 

654 keyValuePairs=keyValuePairs 

655 ) 

656 

657 return graph 

658 

659 

660# ``Function`` has a property named like the class ``CallGraph``, so it annotates its field with this alias. 

661_CallGraph = CallGraph