Coverage for trlc/vcg.py: 97%

773 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-01-15 09:56 +0000

1#!/usr/bin/env python3 

2# 

3# TRLC - Treat Requirements Like Code 

4# Copyright (C) 2023 Bayerische Motoren Werke Aktiengesellschaft (BMW AG) 

5# Copyright (C) 2023-2025 Florian Schanda 

6# 

7# This file is part of the TRLC Python Reference Implementation. 

8# 

9# TRLC is free software: you can redistribute it and/or modify it 

10# under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# TRLC is distributed in the hope that it will be useful, but WITHOUT 

15# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 

16# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 

17# License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with TRLC. If not, see <https://www.gnu.org/licenses/>. 

21 

22import subprocess 

23 

24from trlc.ast import * 

25from trlc.errors import Location, Message_Handler 

26 

27try: 

28 from pyvcg import smt 

29 from pyvcg import graph 

30 from pyvcg import vcg 

31 from pyvcg.driver.file_smtlib import SMTLIB_Generator 

32 from pyvcg.driver.cvc5_smtlib import CVC5_File_Solver 

33 VCG_AVAILABLE = True 

34except ImportError: # pragma: no cover 

35 VCG_AVAILABLE = False 

36 

37try: 

38 from pyvcg.driver.cvc5_api import CVC5_Solver 

39 CVC5_API_AVAILABLE = True 

40except ImportError: # pragma: no cover 

41 CVC5_API_AVAILABLE = False 

42 

43CVC5_OPTIONS = { 

44 "tlimit-per" : 2500, 

45 "seed" : 42, 

46 "sat-random-seed" : 42, 

47} 

48 

49 

50class Unsupported(Exception): # pragma: no cover 

51 # lobster-exclude: Not safety relevant 

52 def __init__(self, node, text): 

53 assert isinstance(node, Node) 

54 assert isinstance(text, str) or text is None 

55 super().__init__() 

56 self.message = "%s not yet supported in VCG" % \ 

57 (text if text else node.__class__.__name__) 

58 self.location = node.location 

59 

60 

61class Feedback: 

62 # lobster-exclude: Not safety relevant 

63 def __init__(self, node, message, kind, expect_unsat=True): 

64 assert isinstance(node, Expression) 

65 assert isinstance(message, str) 

66 assert isinstance(kind, str) 

67 assert isinstance(expect_unsat, bool) 

68 self.node = node 

69 self.message = message 

70 self.kind = "vcg-" + kind 

71 self.expect_unsat = expect_unsat 

72 

73 

74class VCG: 

75 # lobster-exclude: Not safety relevant 

76 def __init__(self, mh, n_ctyp, debug, use_api=True, cvc5_binary=None): 

77 assert VCG_AVAILABLE 

78 assert isinstance(mh, Message_Handler) 

79 assert isinstance(n_ctyp, Composite_Type) 

80 assert isinstance(debug, bool) 

81 assert isinstance(use_api, bool) 

82 assert use_api or isinstance(cvc5_binary, str) 

83 

84 self.mh = mh 

85 self.n_ctyp = n_ctyp 

86 self.debug = debug 

87 self.use_api = use_api 

88 self.cvc5_bin = cvc5_binary 

89 

90 self.vc_name = "trlc-%s-%s" % (n_ctyp.n_package.name, 

91 n_ctyp.name) 

92 

93 self.tmp_id = 0 

94 

95 self.vcg = vcg.VCG() 

96 self.graph = self.vcg.graph 

97 self.start = self.vcg.start 

98 # Current start node, we will update this as we go along. 

99 self.preamble = None 

100 # We do remember the first node where we put all our 

101 # declarations, in case we need to add some more later. 

102 

103 self.constants = {} 

104 self.enumerations = {} 

105 self.tuples = {} 

106 self.records = {} 

107 self.arrays = {} 

108 self.bound_vars = {} 

109 self.qe_vars = {} 

110 self.tuple_base = {} 

111 self.uf_records = {} 

112 

113 self.uf_matches = None 

114 # Pointer to the UF we use for matches. We only generate it 

115 # when we must, as it may affect the logics selected due to 

116 # string theory being used. 

117 

118 self.functional = False 

119 # If set to true, then we ignore validity checks and do not 

120 # create intermediates. We just build the value and validity 

121 # expresions and return them. 

122 

123 self.emit_checks = True 

124 # If set to false, we skip creating checks. 

125 

126 @staticmethod 

127 def flag_unsupported(node, text=None): # pragma: no cover 

128 assert isinstance(node, Node) 

129 raise Unsupported(node, text) 

130 

131 def new_temp_name(self): 

132 self.tmp_id += 1 

133 return "tmp.%u" % self.tmp_id 

134 

135 def get_uf_matches(self): 

136 if self.uf_matches is None: 

137 self.uf_matches = \ 

138 smt.Function("trlc.matches", 

139 smt.BUILTIN_BOOLEAN, 

140 smt.Bound_Variable(smt.BUILTIN_STRING, 

141 "subject"), 

142 smt.Bound_Variable(smt.BUILTIN_STRING, 

143 "regex")) 

144 

145 # Create UF for the matches function (for now, later we 

146 # will deal with regex properly). 

147 self.preamble.add_statement( 

148 smt.Function_Declaration(self.uf_matches)) 

149 

150 return self.uf_matches 

151 

152 def create_return(self, node, s_value, s_valid=None): 

153 assert isinstance(node, Expression) 

154 assert isinstance(s_value, smt.Expression) 

155 assert isinstance(s_valid, smt.Expression) or s_valid is None 

156 

157 if s_valid is None: 157 ↛ 160line 157 didn't jump to line 160 because the condition on line 157 was always true

158 s_valid = smt.Boolean_Literal(True) 

159 

160 if self.functional: 

161 return s_value, s_valid 

162 

163 else: 

164 sym_result = smt.Constant(s_value.sort, 

165 self.new_temp_name()) 

166 self.attach_temp_declaration(node, sym_result, s_value) 

167 

168 return sym_result, s_valid 

169 

170 def attach_validity_check(self, bool_expr, origin): 

171 assert isinstance(bool_expr, smt.Expression) 

172 assert bool_expr.sort is smt.BUILTIN_BOOLEAN 

173 assert isinstance(origin, Expression) 

174 assert not self.functional 

175 

176 if not self.emit_checks: 

177 return 

178 

179 # Attach new graph node advance start 

180 if not bool_expr.is_static_true(): 

181 gn_check = graph.Check(self.graph) 

182 gn_check.add_goal(bool_expr, 

183 Feedback(origin, 

184 "expression could be null", 

185 "evaluation-of-null"), 

186 "validity check for %s" % origin.to_string()) 

187 self.start.add_edge_to(gn_check) 

188 self.start = gn_check 

189 

190 def attach_int_division_check(self, int_expr, origin): 

191 assert isinstance(int_expr, smt.Expression) 

192 assert int_expr.sort is smt.BUILTIN_INTEGER 

193 assert isinstance(origin, Expression) 

194 assert not self.functional 

195 

196 if not self.emit_checks: 196 ↛ 197line 196 didn't jump to line 197 because the condition on line 196 was never true

197 return 

198 

199 # Attach new graph node advance start 

200 gn_check = graph.Check(self.graph) 

201 gn_check.add_goal( 

202 smt.Boolean_Negation( 

203 smt.Comparison("=", int_expr, smt.Integer_Literal(0))), 

204 Feedback(origin, 

205 "divisor could be 0", 

206 "div-by-zero"), 

207 "division by zero check for %s" % origin.to_string()) 

208 self.start.add_edge_to(gn_check) 

209 self.start = gn_check 

210 

211 def attach_real_division_check(self, real_expr, origin): 

212 assert isinstance(real_expr, smt.Expression) 

213 assert real_expr.sort is smt.BUILTIN_REAL 

214 assert isinstance(origin, Expression) 

215 assert not self.functional 

216 

217 if not self.emit_checks: 217 ↛ 218line 217 didn't jump to line 218 because the condition on line 217 was never true

218 return 

219 

220 # Attach new graph node advance start 

221 gn_check = graph.Check(self.graph) 

222 gn_check.add_goal( 

223 smt.Boolean_Negation( 

224 smt.Comparison("=", real_expr, smt.Real_Literal(0))), 

225 Feedback(origin, 

226 "divisor could be 0.0", 

227 "div-by-zero"), 

228 "division by zero check for %s" % origin.to_string()) 

229 self.start.add_edge_to(gn_check) 

230 self.start = gn_check 

231 

232 def attach_index_check(self, seq_expr, index_expr, origin): 

233 assert isinstance(seq_expr, smt.Expression) 

234 assert isinstance(seq_expr.sort, smt.Sequence_Sort) 

235 assert isinstance(index_expr, smt.Expression) 

236 assert index_expr.sort is smt.BUILTIN_INTEGER 

237 assert isinstance(origin, Binary_Expression) 

238 assert origin.operator == Binary_Operator.INDEX 

239 assert not self.functional 

240 

241 if not self.emit_checks: 241 ↛ 242line 241 didn't jump to line 242 because the condition on line 241 was never true

242 return 

243 

244 # Attach new graph node advance start 

245 gn_check = graph.Check(self.graph) 

246 gn_check.add_goal( 

247 smt.Comparison(">=", index_expr, smt.Integer_Literal(0)), 

248 Feedback(origin, 

249 "array index could be less than 0", 

250 "array-index"), 

251 "index lower bound check for %s" % origin.to_string()) 

252 gn_check.add_goal( 

253 smt.Comparison("<", 

254 index_expr, 

255 smt.Sequence_Length(seq_expr)), 

256 Feedback(origin, 

257 "array index could be larger than len(%s)" % 

258 origin.n_lhs.to_string(), 

259 "array-index"), 

260 "index lower bound check for %s" % origin.to_string()) 

261 

262 self.start.add_edge_to(gn_check) 

263 self.start = gn_check 

264 

265 def attach_feasability_check(self, bool_expr, origin): 

266 assert isinstance(bool_expr, smt.Expression) 

267 assert bool_expr.sort is smt.BUILTIN_BOOLEAN 

268 assert isinstance(origin, Expression) 

269 assert not self.functional 

270 

271 if not self.emit_checks: 

272 return 

273 

274 # Attach new graph node advance start 

275 gn_check = graph.Check(self.graph) 

276 gn_check.add_goal(bool_expr, 

277 Feedback(origin, 

278 "expression is always true", 

279 "always-true", 

280 expect_unsat = False), 

281 "feasability check for %s" % origin.to_string()) 

282 self.start.add_edge_to(gn_check) 

283 

284 def attach_assumption(self, bool_expr): 

285 assert isinstance(bool_expr, smt.Expression) 

286 assert bool_expr.sort is smt.BUILTIN_BOOLEAN 

287 assert not self.functional 

288 

289 # Attach new graph node advance start 

290 gn_ass = graph.Assumption(self.graph) 

291 gn_ass.add_statement(smt.Assertion(bool_expr)) 

292 self.start.add_edge_to(gn_ass) 

293 self.start = gn_ass 

294 

295 def attach_temp_declaration(self, node, sym, value=None): 

296 assert isinstance(node, (Expression, Action)) 

297 assert isinstance(sym, smt.Constant) 

298 assert isinstance(value, smt.Expression) or value is None 

299 assert not self.functional 

300 

301 # Attach new graph node advance start 

302 gn_decl = graph.Assumption(self.graph) 

303 gn_decl.add_statement( 

304 smt.Constant_Declaration( 

305 symbol = sym, 

306 value = value, 

307 comment = "result of %s at %s" % (node.to_string(), 

308 node.location.to_string()), 

309 relevant = False)) 

310 self.start.add_edge_to(gn_decl) 

311 self.start = gn_decl 

312 

313 def attach_empty_assumption(self): 

314 assert not self.functional 

315 

316 # Attach new graph node advance start 

317 gn_decl = graph.Assumption(self.graph) 

318 self.start.add_edge_to(gn_decl) 

319 self.start = gn_decl 

320 

321 def analyze(self): 

322 try: 

323 self.checks_on_composite_type(self.n_ctyp) 

324 except Unsupported as exc: # pragma: no cover 

325 self.mh.warning(exc.location, 

326 exc.message) 

327 

328 def checks_on_composite_type(self, n_ctyp): 

329 assert isinstance(n_ctyp, Composite_Type) 

330 

331 # Create node for global declarations 

332 gn_locals = graph.Assumption(self.graph) 

333 self.start.add_edge_to(gn_locals) 

334 self.start = gn_locals 

335 self.preamble = gn_locals 

336 

337 # Create local variables 

338 for n_component in n_ctyp.all_components(): 

339 self.tr_component_decl(n_component, self.start) 

340 

341 # Create paths for checks 

342 for n_check in n_ctyp.iter_checks(): 

343 current_start = self.start 

344 self.tr_check(n_check) 

345 

346 # Only fatal checks contribute to the total knowledge 

347 if n_check.severity != "fatal": 

348 self.start = current_start 

349 

350 # Emit debug graph 

351 if self.debug: # pragma: no cover 

352 subprocess.run(["dot", "-Tpdf", "-o%s.pdf" % self.vc_name], 

353 input = self.graph.debug_render_dot(), 

354 check = True, 

355 encoding = "UTF-8") 

356 

357 # Generate VCs 

358 self.vcg.generate() 

359 

360 # Solve VCs and provide feedback 

361 nok_feasibility_checks = [] 

362 ok_feasibility_checks = set() 

363 nok_validity_checks = set() 

364 

365 for vc_id, vc in enumerate(self.vcg.vcs): 

366 if self.debug: # pramga: no cover 366 ↛ 367line 366 didn't jump to line 367 because the condition on line 366 was never true

367 with open(self.vc_name + "_%04u.smt2" % vc_id, "w", 

368 encoding="UTF-8") as fd: 

369 fd.write(vc["script"].generate_vc(SMTLIB_Generator())) 

370 

371 # Checks that have already failed don't need to be checked 

372 # again on a different path 

373 if vc["feedback"].expect_unsat and \ 

374 vc["feedback"] in nok_validity_checks: 

375 continue 

376 

377 if self.use_api: 

378 solver = CVC5_Solver() 

379 else: 

380 solver = CVC5_File_Solver(self.cvc5_bin) 

381 for name, value in CVC5_OPTIONS.items(): 

382 solver.set_solver_option(name, value) 

383 

384 status, values = vc["script"].solve_vc(solver) 

385 

386 message = vc["feedback"].message 

387 if self.debug: # pragma: no cover 

388 message += " [vc_id = %u]" % vc_id 

389 

390 if vc["feedback"].expect_unsat: 

391 if status != "unsat": 

392 self.mh.check(vc["feedback"].node.location, 

393 message, 

394 vc["feedback"].kind, 

395 self.create_counterexample(status, 

396 values)) 

397 nok_validity_checks.add(vc["feedback"]) 

398 else: 

399 if status == "unsat": 

400 nok_feasibility_checks.append(vc["feedback"]) 

401 else: 

402 ok_feasibility_checks.add(vc["feedback"]) 

403 

404 # This is a bit wonky, but this way we make sure the ording is 

405 # consistent 

406 for feedback in nok_feasibility_checks: 

407 if feedback not in ok_feasibility_checks: 

408 self.mh.check(feedback.node.location, 

409 feedback.message, 

410 feedback.kind) 

411 ok_feasibility_checks.add(feedback) 

412 

413 def create_counterexample(self, status, values): 

414 rv = [ 

415 "example %s triggering error:" % 

416 self.n_ctyp.__class__.__name__.lower(), 

417 " %s bad_potato {" % self.n_ctyp.name 

418 ] 

419 

420 for n_component in self.n_ctyp.all_components(): 

421 id_value = self.tr_component_value_name(n_component) 

422 id_valid = self.tr_component_valid_name(n_component) 

423 if status == "unknown" and (id_value not in values or 423 ↛ 425line 423 didn't jump to line 425 because the condition on line 423 was never true

424 id_valid not in values): 

425 rv.append(" %s = ???" % n_component.name) 

426 elif values.get(id_valid): 

427 rv.append(" %s = %s" % 

428 (n_component.name, 

429 self.value_to_trlc(n_component.n_typ, 

430 values[id_value]))) 

431 else: 

432 rv.append(" /* %s is null */" % n_component.name) 

433 

434 rv.append(" }") 

435 if status == "unknown": 

436 rv.append("/* note: counter-example is unreliable in this case */") 

437 return "\n".join(rv) 

438 

439 def fraction_to_decimal_string(self, num, den): 

440 assert isinstance(num, int) 

441 assert isinstance(den, int) and den >= 1 

442 

443 tmp = den 

444 if tmp > 2: 

445 while tmp > 1: 

446 if tmp % 2 == 0: 

447 tmp = tmp // 2 

448 elif tmp % 5 == 0: 

449 tmp = tmp // 5 

450 else: 

451 return "%i / %u" % (num, den) 

452 

453 rv = str(abs(num) // den) 

454 

455 i = abs(num) % den 

456 j = den 

457 

458 if i > 0: 

459 rv += "." 

460 while i > 0: 

461 i *= 10 

462 rv += str(i // j) 

463 i = i % j 

464 else: 

465 rv += ".0" 

466 

467 if num < 0: 

468 return "-" + rv 

469 else: 

470 return rv 

471 

472 def value_to_trlc(self, n_typ, value): 

473 assert isinstance(n_typ, Type) 

474 

475 if isinstance(n_typ, Builtin_Integer): 

476 return str(value) 

477 

478 elif isinstance(n_typ, Builtin_Decimal): 

479 if isinstance(value, Fraction): 

480 num, den = value.as_integer_ratio() 

481 if den >= 1: 481 ↛ 484line 481 didn't jump to line 484 because the condition on line 481 was always true

482 return self.fraction_to_decimal_string(num, den) 

483 else: 

484 return self.fraction_to_decimal_string(-num, -den) 

485 else: 

486 return "/* unable to generate precise value */" 

487 

488 elif isinstance(n_typ, Builtin_Boolean): 

489 return "true" if value else "false" 

490 

491 elif isinstance(n_typ, Enumeration_Type): 

492 return n_typ.name + "." + value 

493 

494 elif isinstance(n_typ, Builtin_String): 

495 if "\n" in value: 

496 return "'''%s'''" % value 

497 else: 

498 return '"%s"' % value 

499 

500 elif isinstance(n_typ, Record_Type): 

501 if value < 0: 

502 instance_id = value * -2 - 1 

503 else: 

504 instance_id = value * 2 

505 if n_typ.n_package is self.n_ctyp.n_package: 

506 return "%s_instance_%i" % (n_typ.name, instance_id) 

507 else: 

508 return "%s.%s_instance_%i" % (n_typ.n_package.name, 

509 n_typ.name, 

510 instance_id) 

511 

512 elif isinstance(n_typ, Tuple_Type): 

513 parts = [] 

514 for n_item in n_typ.iter_sequence(): 

515 if isinstance(n_item, Composite_Component): 

516 if n_item.optional and not value[n_item.name + ".valid"]: 

517 parts.pop() 

518 break 

519 parts.append( 

520 self.value_to_trlc(n_item.n_typ, 

521 value[n_item.name + ".value"])) 

522 

523 else: 

524 assert isinstance(n_item, Separator) 

525 sep_text = { 

526 "AT" : "@", 

527 "COLON" : ":", 

528 "SEMICOLON" : ";" 

529 }.get(n_item.token.kind, n_item.token.value) 

530 parts.append(sep_text) 

531 

532 if n_typ.has_separators(): 532 ↛ 535line 532 didn't jump to line 535 because the condition on line 532 was always true

533 return "".join(parts) 

534 else: 

535 return "(%s)" % ", ".join(parts) 

536 

537 elif isinstance(n_typ, Array_Type): 

538 return "[%s]" % ", ".join(self.value_to_trlc(n_typ.element_type, 

539 item) 

540 for item in value) 

541 

542 else: # pragma: no cover 

543 self.flag_unsupported(n_typ, 

544 "back-conversion from %s" % n_typ.name) 

545 

546 def tr_component_value_name(self, n_component): 

547 return n_component.member_of.fully_qualified_name() + \ 

548 "." + n_component.name + ".value" 

549 

550 def tr_component_valid_name(self, n_component): 

551 return n_component.member_of.fully_qualified_name() + \ 

552 "." + n_component.name + ".valid" 

553 

554 def emit_tuple_constraints(self, n_tuple, s_sym): 

555 assert isinstance(n_tuple, Tuple_Type) 

556 assert isinstance(s_sym, smt.Constant) 

557 

558 old_functional, self.functional = self.functional, True 

559 self.tuple_base[n_tuple] = s_sym 

560 

561 constraints = [] 

562 

563 # The first tuple constraint is that all checks must have 

564 # passed, otherwise the tool would just error. An error in a 

565 # tuple is pretty much the same as a fatal in the enclosing 

566 # record. 

567 

568 for n_check in n_tuple.iter_checks(): 

569 if n_check.severity == "warning": 

570 continue 

571 # We do consider both fatal and errors to be sources of 

572 # truth here. 

573 c_value, _ = self.tr_expression(n_check.n_expr) 

574 constraints.append(c_value) 

575 

576 # The secopnd tuple constraint is that once you get a null 

577 # field, all following fields must also be null. 

578 

579 components = n_tuple.all_components() 

580 for i, component in enumerate(components): 

581 if component.optional: 

582 condition = smt.Boolean_Negation( 

583 smt.Record_Access(s_sym, 

584 component.name + ".valid")) 

585 consequences = [ 

586 smt.Boolean_Negation( 

587 smt.Record_Access(s_sym, 

588 c.name + ".valid")) 

589 for c in components[i + 1:] 

590 ] 

591 if len(consequences) == 0: 

592 break 

593 elif len(consequences) == 1: 593 ↛ 596line 593 didn't jump to line 596 because the condition on line 593 was always true

594 consequence = consequences[0] 

595 else: 

596 consequence = smt.Conjunction(*consequences) 

597 constraints.append(smt.Implication(condition, consequence)) 

598 

599 del self.tuple_base[n_tuple] 

600 self.functional = old_functional 

601 

602 for cons in constraints: 

603 self.start.add_statement(smt.Assertion(cons)) 

604 

605 def tr_component_decl(self, n_component, gn_locals): 

606 assert isinstance(n_component, Composite_Component) 

607 assert isinstance(gn_locals, graph.Assumption) 

608 

609 if isinstance(self.n_ctyp, Record_Type): 

610 frozen = self.n_ctyp.is_frozen(n_component) 

611 else: 

612 frozen = False 

613 

614 id_value = self.tr_component_value_name(n_component) 

615 s_sort = self.tr_type(n_component.n_typ) 

616 s_sym = smt.Constant(s_sort, id_value) 

617 if frozen: 

618 old_functional, self.functional = self.functional, True 

619 s_val, _ = self.tr_expression( 

620 self.n_ctyp.get_freezing_expression(n_component)) 

621 self.functional = old_functional 

622 else: 

623 s_val = None 

624 s_decl = smt.Constant_Declaration( 

625 symbol = s_sym, 

626 value = s_val, 

627 comment = "value for %s declared on %s" % ( 

628 n_component.name, 

629 n_component.location.to_string()), 

630 relevant = True) 

631 gn_locals.add_statement(s_decl) 

632 self.constants[id_value] = s_sym 

633 

634 if isinstance(n_component.n_typ, Tuple_Type): 

635 self.emit_tuple_constraints(n_component.n_typ, s_sym) 

636 

637 # For arrays we need to add additional constraints for the 

638 # length 

639 if isinstance(n_component.n_typ, Array_Type): 

640 if n_component.n_typ.lower_bound > 0: 

641 s_lower = smt.Integer_Literal(n_component.n_typ.lower_bound) 

642 gn_locals.add_statement( 

643 smt.Assertion( 

644 smt.Comparison(">=", 

645 smt.Sequence_Length(s_sym), 

646 s_lower))) 

647 

648 if n_component.n_typ.upper_bound is not None: 

649 s_upper = smt.Integer_Literal(n_component.n_typ.upper_bound) 

650 gn_locals.add_statement( 

651 smt.Assertion( 

652 smt.Comparison("<=", 

653 smt.Sequence_Length(s_sym), 

654 s_upper))) 

655 

656 id_valid = self.tr_component_valid_name(n_component) 

657 s_sym = smt.Constant(smt.BUILTIN_BOOLEAN, id_valid) 

658 s_val = (None 

659 if n_component.optional and not frozen 

660 else smt.Boolean_Literal(True)) 

661 s_decl = smt.Constant_Declaration( 

662 symbol = s_sym, 

663 value = s_val, 

664 relevant = True) 

665 gn_locals.add_statement(s_decl) 

666 self.constants[id_valid] = s_sym 

667 

668 def tr_type(self, n_type): 

669 assert isinstance(n_type, Type) 

670 

671 if isinstance(n_type, Builtin_Boolean): 

672 return smt.BUILTIN_BOOLEAN 

673 

674 elif isinstance(n_type, Builtin_Integer): 

675 return smt.BUILTIN_INTEGER 

676 

677 elif isinstance(n_type, Builtin_Decimal): 

678 return smt.BUILTIN_REAL 

679 

680 elif isinstance(n_type, Builtin_String): 

681 return smt.BUILTIN_STRING 

682 

683 elif isinstance(n_type, Enumeration_Type): 

684 if n_type not in self.enumerations: 

685 s_sort = smt.Enumeration(n_type.n_package.name + 

686 "." + n_type.name) 

687 for n_lit in n_type.literals.values(): 

688 s_sort.add_literal(n_lit.name) 

689 self.enumerations[n_type] = s_sort 

690 self.start.add_statement( 

691 smt.Enumeration_Declaration( 

692 s_sort, 

693 "enumeration %s from %s" % ( 

694 n_type.name, 

695 n_type.location.to_string()))) 

696 return self.enumerations[n_type] 

697 

698 elif isinstance(n_type, Tuple_Type): 

699 if n_type not in self.tuples: 

700 s_sort = smt.Record(n_type.n_package.name + 

701 "." + n_type.name) 

702 for n_component in n_type.all_components(): 

703 s_sort.add_component(n_component.name + ".value", 

704 self.tr_type(n_component.n_typ)) 

705 if n_component.optional: 

706 s_sort.add_component(n_component.name + ".valid", 

707 smt.BUILTIN_BOOLEAN) 

708 self.tuples[n_type] = s_sort 

709 self.start.add_statement( 

710 smt.Record_Declaration( 

711 s_sort, 

712 "tuple %s from %s" % ( 

713 n_type.name, 

714 n_type.location.to_string()))) 

715 

716 return self.tuples[n_type] 

717 

718 elif isinstance(n_type, Array_Type): 

719 if n_type not in self.arrays: 

720 s_element_sort = self.tr_type(n_type.element_type) 

721 s_sequence = smt.Sequence_Sort(s_element_sort) 

722 self.arrays[n_type] = s_sequence 

723 

724 return self.arrays[n_type] 

725 

726 elif isinstance(n_type, Record_Type): 

727 # Record references are modelled as a free integer. If we 

728 # access their field then we use an uninterpreted 

729 # function. Some of these have special meaning: 

730 # 0 - the null reference 

731 # 1 - the self reference 

732 # anything else - uninterpreted 

733 return smt.BUILTIN_INTEGER 

734 

735 else: # pragma: no cover 

736 self.flag_unsupported(n_type) 

737 

738 def tr_check(self, n_check): 

739 assert isinstance(n_check, Check) 

740 

741 # If the check belongs to a different type then we are looking 

742 # at a type extension. In this case we do not create checks 

743 # again, because if a check would fail it would already have 

744 # failed. 

745 if n_check.n_type is not self.n_ctyp: 

746 old_emit, self.emit_checks = self.emit_checks, False 

747 

748 value, valid = self.tr_expression(n_check.n_expr) 

749 self.attach_validity_check(valid, n_check.n_expr) 

750 self.attach_feasability_check(value, n_check.n_expr) 

751 self.attach_assumption(value) 

752 

753 if n_check.n_type is not self.n_ctyp: 

754 self.emit_checks = old_emit 

755 

756 def tr_expression(self, n_expr): 

757 value = None 

758 

759 if isinstance(n_expr, Name_Reference): 

760 return self.tr_name_reference(n_expr) 

761 

762 elif isinstance(n_expr, Unary_Expression): 

763 return self.tr_unary_expression(n_expr) 

764 

765 elif isinstance(n_expr, Binary_Expression): 

766 return self.tr_binary_expression(n_expr) 

767 

768 elif isinstance(n_expr, Range_Test): 

769 return self.tr_range_test(n_expr) 

770 

771 elif isinstance(n_expr, OneOf_Expression): 

772 return self.tr_oneof_test(n_expr) 

773 

774 elif isinstance(n_expr, Conditional_Expression): 

775 if self.functional: 

776 return self.tr_conditional_expression_functional(n_expr) 

777 else: 

778 return self.tr_conditional_expression(n_expr) 

779 

780 elif isinstance(n_expr, Null_Literal): 

781 return None, smt.Boolean_Literal(False) 

782 

783 elif isinstance(n_expr, Boolean_Literal): 

784 value = smt.Boolean_Literal(n_expr.value) 

785 

786 elif isinstance(n_expr, Integer_Literal): 

787 value = smt.Integer_Literal(n_expr.value) 

788 

789 elif isinstance(n_expr, Decimal_Literal): 

790 value = smt.Real_Literal(n_expr.value) 

791 

792 elif isinstance(n_expr, Enumeration_Literal): 

793 value = smt.Enumeration_Literal(self.tr_type(n_expr.typ), 

794 n_expr.value.name) 

795 

796 elif isinstance(n_expr, String_Literal): 

797 value = smt.String_Literal(n_expr.value) 

798 

799 elif isinstance(n_expr, Quantified_Expression): 

800 return self.tr_quantified_expression(n_expr) 

801 

802 elif isinstance(n_expr, Field_Access_Expression): 

803 return self.tr_field_access_expression(n_expr) 

804 

805 else: # pragma: no cover 

806 self.flag_unsupported(n_expr) 

807 

808 return value, smt.Boolean_Literal(True) 

809 

810 def tr_name_reference(self, n_ref): 

811 assert isinstance(n_ref, Name_Reference) 

812 

813 if isinstance(n_ref.entity, Composite_Component): 

814 if n_ref.entity.member_of in self.tuple_base: 

815 sym = self.tuple_base[n_ref.entity.member_of] 

816 if n_ref.entity.optional: 

817 s_valid = smt.Record_Access(sym, 

818 n_ref.entity.name + ".valid") 

819 else: 

820 s_valid = smt.Boolean_Literal(True) 

821 s_value = smt.Record_Access(sym, 

822 n_ref.entity.name + ".value") 

823 return s_value, s_valid 

824 

825 else: 

826 id_value = self.tr_component_value_name(n_ref.entity) 

827 id_valid = self.tr_component_valid_name(n_ref.entity) 

828 return self.constants[id_value], self.constants[id_valid] 

829 

830 else: 

831 assert isinstance(n_ref.entity, Quantified_Variable) 

832 if n_ref.entity in self.qe_vars: 

833 return self.qe_vars[n_ref.entity], smt.Boolean_Literal(True) 

834 else: 

835 return self.bound_vars[n_ref.entity], smt.Boolean_Literal(True) 

836 

837 def tr_unary_expression(self, n_expr): 

838 assert isinstance(n_expr, Unary_Expression) 

839 

840 operand_value, operand_valid = self.tr_expression(n_expr.n_operand) 

841 if not self.functional: 

842 self.attach_validity_check(operand_valid, n_expr.n_operand) 

843 

844 sym_value = None 

845 

846 if n_expr.operator == Unary_Operator.MINUS: 

847 if isinstance(n_expr.n_operand.typ, Builtin_Integer): 

848 sym_value = smt.Unary_Int_Arithmetic_Op("-", 

849 operand_value) 

850 else: 

851 assert isinstance(n_expr.n_operand.typ, Builtin_Decimal) 

852 sym_value = smt.Unary_Real_Arithmetic_Op("-", 

853 operand_value) 

854 

855 elif n_expr.operator == Unary_Operator.PLUS: 

856 sym_value = operand_value 

857 

858 elif n_expr.operator == Unary_Operator.LOGICAL_NOT: 

859 sym_value = smt.Boolean_Negation(operand_value) 

860 

861 elif n_expr.operator == Unary_Operator.ABSOLUTE_VALUE: 

862 if isinstance(n_expr.n_operand.typ, Builtin_Integer): 

863 sym_value = smt.Unary_Int_Arithmetic_Op("abs", 

864 operand_value) 

865 

866 else: 

867 assert isinstance(n_expr.n_operand.typ, Builtin_Decimal) 

868 sym_value = smt.Unary_Real_Arithmetic_Op("abs", 

869 operand_value) 

870 

871 elif n_expr.operator == Unary_Operator.STRING_LENGTH: 

872 sym_value = smt.String_Length(operand_value) 

873 

874 elif n_expr.operator == Unary_Operator.ARRAY_LENGTH: 

875 sym_value = smt.Sequence_Length(operand_value) 

876 

877 elif n_expr.operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

878 sym_value = smt.Conversion_To_Real(operand_value) 

879 

880 elif n_expr.operator == Unary_Operator.CONVERSION_TO_INT: 

881 sym_value = smt.Conversion_To_Integer("rna", operand_value) 

882 

883 else: 

884 self.mh.ice_loc(n_expr, 

885 "unexpected unary operator %s" % 

886 n_expr.operator.name) 

887 

888 return self.create_return(n_expr, sym_value) 

889 

890 def tr_binary_expression(self, n_expr): 

891 assert isinstance(n_expr, Binary_Expression) 

892 

893 # Some operators deal with validity in a different way. We 

894 # deal with them first and then exit. 

895 if n_expr.operator in (Binary_Operator.COMP_EQ, 

896 Binary_Operator.COMP_NEQ): 

897 return self.tr_op_equality(n_expr) 

898 

899 elif n_expr.operator == Binary_Operator.LOGICAL_IMPLIES: 

900 return self.tr_op_implication(n_expr) 

901 

902 elif n_expr.operator == Binary_Operator.LOGICAL_AND: 

903 return self.tr_op_and(n_expr) 

904 

905 elif n_expr.operator == Binary_Operator.LOGICAL_OR: 

906 return self.tr_op_or(n_expr) 

907 

908 # The remaining operators always check for validity, so we can 

909 # obtain the values of both sides now. 

910 lhs_value, lhs_valid = self.tr_expression(n_expr.n_lhs) 

911 if not self.functional: 

912 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

913 rhs_value, rhs_valid = self.tr_expression(n_expr.n_rhs) 

914 if not self.functional: 

915 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

916 sym_value = None 

917 

918 if n_expr.operator == Binary_Operator.LOGICAL_XOR: 

919 sym_value = smt.Exclusive_Disjunction(lhs_value, rhs_value) 

920 

921 elif n_expr.operator in (Binary_Operator.PLUS, 

922 Binary_Operator.MINUS, 

923 Binary_Operator.TIMES, 

924 Binary_Operator.DIVIDE, 

925 Binary_Operator.REMAINDER): 

926 

927 if isinstance(n_expr.n_lhs.typ, Builtin_String): 

928 assert n_expr.operator == Binary_Operator.PLUS 

929 sym_value = smt.String_Concatenation(lhs_value, rhs_value) 

930 

931 elif isinstance(n_expr.n_lhs.typ, Builtin_Integer): 

932 if n_expr.operator in (Binary_Operator.DIVIDE, 

933 Binary_Operator.REMAINDER): 

934 self.attach_int_division_check(rhs_value, n_expr) 

935 

936 smt_op = { 

937 Binary_Operator.PLUS : "+", 

938 Binary_Operator.MINUS : "-", 

939 Binary_Operator.TIMES : "*", 

940 Binary_Operator.DIVIDE : "floor_div", 

941 Binary_Operator.REMAINDER : "ada_remainder", 

942 }[n_expr.operator] 

943 

944 sym_value = smt.Binary_Int_Arithmetic_Op(smt_op, 

945 lhs_value, 

946 rhs_value) 

947 

948 else: 

949 assert isinstance(n_expr.n_lhs.typ, Builtin_Decimal) 

950 if n_expr.operator == Binary_Operator.DIVIDE: 

951 self.attach_real_division_check(rhs_value, n_expr) 

952 

953 smt_op = { 

954 Binary_Operator.PLUS : "+", 

955 Binary_Operator.MINUS : "-", 

956 Binary_Operator.TIMES : "*", 

957 Binary_Operator.DIVIDE : "/", 

958 }[n_expr.operator] 

959 

960 sym_value = smt.Binary_Real_Arithmetic_Op(smt_op, 

961 lhs_value, 

962 rhs_value) 

963 

964 elif n_expr.operator in (Binary_Operator.COMP_LT, 

965 Binary_Operator.COMP_LEQ, 

966 Binary_Operator.COMP_GT, 

967 Binary_Operator.COMP_GEQ): 

968 smt_op = { 

969 Binary_Operator.COMP_LT : "<", 

970 Binary_Operator.COMP_LEQ : "<=", 

971 Binary_Operator.COMP_GT : ">", 

972 Binary_Operator.COMP_GEQ : ">=", 

973 }[n_expr.operator] 

974 

975 sym_value = smt.Comparison(smt_op, lhs_value, rhs_value) 

976 

977 elif n_expr.operator in (Binary_Operator.STRING_CONTAINS, 

978 Binary_Operator.STRING_STARTSWITH, 

979 Binary_Operator.STRING_ENDSWITH): 

980 

981 smt_op = { 

982 Binary_Operator.STRING_CONTAINS : "contains", 

983 Binary_Operator.STRING_STARTSWITH : "prefixof", 

984 Binary_Operator.STRING_ENDSWITH : "suffixof" 

985 } 

986 

987 # LHS / RHS ordering is not a mistake, in SMTLIB it's the 

988 # other way around than in TRLC. 

989 sym_value = smt.String_Predicate(smt_op[n_expr.operator], 

990 rhs_value, 

991 lhs_value) 

992 

993 elif n_expr.operator == Binary_Operator.STRING_REGEX: 

994 rhs_evaluation = n_expr.n_rhs.evaluate(self.mh, None, None).value 

995 assert isinstance(rhs_evaluation, str) 

996 

997 sym_value = smt.Function_Application( 

998 self.get_uf_matches(), 

999 lhs_value, 

1000 smt.String_Literal(rhs_evaluation)) 

1001 

1002 elif n_expr.operator == Binary_Operator.INDEX: 

1003 self.attach_index_check(lhs_value, rhs_value, n_expr) 

1004 sym_value = smt.Sequence_Index(lhs_value, rhs_value) 

1005 

1006 elif n_expr.operator == Binary_Operator.ARRAY_CONTAINS: 

1007 sym_value = smt.Sequence_Contains(rhs_value, lhs_value) 

1008 

1009 elif n_expr.operator == Binary_Operator.POWER: 

1010 # LRM says that the exponent is always static and an 

1011 # integer 

1012 static_value = n_expr.n_rhs.evaluate(self.mh, None, None).value 

1013 assert isinstance(static_value, int) and static_value >= 0 

1014 

1015 if static_value == 0: 1015 ↛ 1016line 1015 didn't jump to line 1016 because the condition on line 1015 was never true

1016 if isinstance(n_expr.n_lhs.typ, Builtin_Integer): 

1017 sym_value = smt.Integer_Literal(1) 

1018 else: 

1019 assert isinstance(n_expr.n_lhs.typ, Builtin_Decimal) 

1020 sym_value = smt.Real_Literal(1) 

1021 

1022 else: 

1023 sym_value = lhs_value 

1024 for _ in range(1, static_value): 

1025 if isinstance(n_expr.n_lhs.typ, Builtin_Integer): 

1026 sym_value = smt.Binary_Int_Arithmetic_Op("*", 

1027 sym_value, 

1028 lhs_value) 

1029 else: 

1030 assert isinstance(n_expr.n_lhs.typ, Builtin_Decimal) 

1031 sym_value = smt.Binary_Real_Arithmetic_Op("*", 

1032 sym_value, 

1033 lhs_value) 

1034 

1035 else: # pragma: no cover 

1036 self.flag_unsupported(n_expr, n_expr.operator.name) 

1037 

1038 return self.create_return(n_expr, sym_value) 

1039 

1040 def tr_range_test(self, n_expr): 

1041 assert isinstance(n_expr, Range_Test) 

1042 

1043 lhs_value, lhs_valid = self.tr_expression(n_expr.n_lhs) 

1044 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1045 lower_value, lower_valid = self.tr_expression(n_expr.n_lower) 

1046 self.attach_validity_check(lower_valid, n_expr.n_lower) 

1047 upper_value, upper_valid = self.tr_expression(n_expr.n_upper) 

1048 self.attach_validity_check(upper_valid, n_expr.n_upper) 

1049 

1050 sym_value = smt.Conjunction( 

1051 smt.Comparison(">=", lhs_value, lower_value), 

1052 smt.Comparison("<=", lhs_value, upper_value)) 

1053 

1054 return self.create_return(n_expr, sym_value) 

1055 

1056 def tr_oneof_test(self, n_expr): 

1057 assert isinstance(n_expr, OneOf_Expression) 

1058 

1059 choices = [] 

1060 for n_choice in n_expr.choices: 

1061 c_value, c_valid = self.tr_expression(n_choice) 

1062 self.attach_validity_check(c_valid, n_choice) 

1063 choices.append(c_value) 

1064 

1065 negated_choices = [smt.Boolean_Negation(c) 

1066 for c in choices] 

1067 

1068 # pylint: disable=consider-using-enumerate 

1069 

1070 if len(choices) == 1: 

1071 result = choices[0] 

1072 elif len(choices) == 2: 

1073 result = smt.Exclusive_Disjunction(choices[0], choices[1]) 

1074 else: 

1075 assert len(choices) >= 3 

1076 values = [] 

1077 for choice_id in range(len(choices)): 

1078 sequence = [] 

1079 for other_id in range(len(choices)): 

1080 if other_id == choice_id: 

1081 sequence.append(choices[other_id]) 

1082 else: 

1083 sequence.append(negated_choices[other_id]) 

1084 values.append(smt.Conjunction(*sequence)) 

1085 result = smt.Disjunction(*values) 

1086 

1087 return self.create_return(n_expr, result) 

1088 

1089 def tr_conditional_expression_functional(self, n_expr): 

1090 assert isinstance(n_expr, Conditional_Expression) 

1091 

1092 s_result, _ = self.tr_expression(n_expr.else_expr) 

1093 for n_action in reversed(n_expr.actions): 

1094 s_condition, _ = self.tr_expression(n_action.n_cond) 

1095 s_true, _ = self.tr_expression(n_action.n_expr) 

1096 s_result = smt.Conditional(s_condition, s_true, s_result) 

1097 

1098 return self.create_return(n_expr, s_result) 

1099 

1100 def tr_conditional_expression(self, n_expr): 

1101 assert isinstance(n_expr, Conditional_Expression) 

1102 assert not self.functional 

1103 

1104 gn_end = graph.Node(self.graph) 

1105 sym_result = smt.Constant(self.tr_type(n_expr.typ), 

1106 self.new_temp_name()) 

1107 

1108 for n_action in n_expr.actions: 

1109 test_value, test_valid = self.tr_expression(n_action.n_cond) 

1110 self.attach_validity_check(test_valid, n_action.n_cond) 

1111 current_start = self.start 

1112 

1113 # Create path where action is true 

1114 self.attach_assumption(test_value) 

1115 res_value, res_valid = self.tr_expression(n_action.n_expr) 

1116 self.attach_validity_check(res_valid, n_action.n_expr) 

1117 self.attach_temp_declaration(n_action, 

1118 sym_result, 

1119 res_value) 

1120 self.start.add_edge_to(gn_end) 

1121 

1122 # Reset to test and proceed with the other actions 

1123 self.start = current_start 

1124 self.attach_assumption(smt.Boolean_Negation(test_value)) 

1125 

1126 # Finally execute the else part 

1127 res_value, res_valid = self.tr_expression(n_expr.else_expr) 

1128 self.attach_validity_check(res_valid, n_expr.else_expr) 

1129 self.attach_temp_declaration(n_expr, 

1130 sym_result, 

1131 res_value) 

1132 self.start.add_edge_to(gn_end) 

1133 

1134 # And join 

1135 self.start = gn_end 

1136 return sym_result, smt.Boolean_Literal(True) 

1137 

1138 def tr_op_implication(self, n_expr): 

1139 assert isinstance(n_expr, Binary_Expression) 

1140 assert n_expr.operator == Binary_Operator.LOGICAL_IMPLIES 

1141 

1142 if self.functional: 

1143 lhs_value, _ = self.tr_expression(n_expr.n_lhs) 

1144 rhs_value, _ = self.tr_expression(n_expr.n_rhs) 

1145 return self.create_return(n_expr, 

1146 smt.Implication(lhs_value, rhs_value)) 

1147 

1148 lhs_value, lhs_valid = self.tr_expression(n_expr.n_lhs) 

1149 # Emit VC for validity 

1150 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1151 

1152 # Split into two paths. 

1153 current_start = self.start 

1154 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, 

1155 self.new_temp_name()) 

1156 gn_end = graph.Node(self.graph) 

1157 

1158 ### 1: Implication is not valid 

1159 self.start = current_start 

1160 self.attach_assumption(smt.Boolean_Negation(lhs_value)) 

1161 self.attach_temp_declaration(n_expr, 

1162 sym_result, 

1163 smt.Boolean_Literal(True)) 

1164 self.start.add_edge_to(gn_end) 

1165 

1166 ### 2: Implication is valid. 

1167 self.start = current_start 

1168 self.attach_assumption(lhs_value) 

1169 rhs_value, rhs_valid = self.tr_expression(n_expr.n_rhs) 

1170 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1171 self.attach_temp_declaration(n_expr, 

1172 sym_result, 

1173 rhs_value) 

1174 self.start.add_edge_to(gn_end) 

1175 

1176 # Join paths 

1177 self.start = gn_end 

1178 

1179 return sym_result, smt.Boolean_Literal(True) 

1180 

1181 def tr_op_and(self, n_expr): 

1182 assert isinstance(n_expr, Binary_Expression) 

1183 assert n_expr.operator == Binary_Operator.LOGICAL_AND 

1184 

1185 if self.functional: 

1186 lhs_value, _ = self.tr_expression(n_expr.n_lhs) 

1187 rhs_value, _ = self.tr_expression(n_expr.n_rhs) 

1188 return self.create_return(n_expr, 

1189 smt.Conjunction(lhs_value, rhs_value)) 

1190 

1191 lhs_value, lhs_valid = self.tr_expression(n_expr.n_lhs) 

1192 # Emit VC for validity 

1193 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1194 

1195 # Split into two paths. 

1196 current_start = self.start 

1197 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, 

1198 self.new_temp_name()) 

1199 gn_end = graph.Node(self.graph) 

1200 

1201 ### 1: LHS is not true 

1202 self.start = current_start 

1203 self.attach_assumption(smt.Boolean_Negation(lhs_value)) 

1204 self.attach_temp_declaration(n_expr, 

1205 sym_result, 

1206 smt.Boolean_Literal(False)) 

1207 self.start.add_edge_to(gn_end) 

1208 

1209 ### 2: LHS is true 

1210 self.start = current_start 

1211 self.attach_assumption(lhs_value) 

1212 rhs_value, rhs_valid = self.tr_expression(n_expr.n_rhs) 

1213 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1214 self.attach_temp_declaration(n_expr, 

1215 sym_result, 

1216 rhs_value) 

1217 self.start.add_edge_to(gn_end) 

1218 

1219 # Join paths 

1220 self.start = gn_end 

1221 

1222 return sym_result, smt.Boolean_Literal(True) 

1223 

1224 def tr_op_or(self, n_expr): 

1225 assert isinstance(n_expr, Binary_Expression) 

1226 assert n_expr.operator == Binary_Operator.LOGICAL_OR 

1227 

1228 if self.functional: 1228 ↛ 1229line 1228 didn't jump to line 1229 because the condition on line 1228 was never true

1229 lhs_value, _ = self.tr_expression(n_expr.n_lhs) 

1230 rhs_value, _ = self.tr_expression(n_expr.n_rhs) 

1231 return self.create_return(n_expr, 

1232 smt.Disjunction(lhs_value, rhs_value)) 

1233 

1234 lhs_value, lhs_valid = self.tr_expression(n_expr.n_lhs) 

1235 # Emit VC for validity 

1236 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1237 

1238 # Split into two paths. 

1239 current_start = self.start 

1240 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, 

1241 self.new_temp_name()) 

1242 gn_end = graph.Node(self.graph) 

1243 

1244 ### 1: LHS is true 

1245 self.start = current_start 

1246 self.attach_assumption(lhs_value) 

1247 self.attach_temp_declaration(n_expr, 

1248 sym_result, 

1249 smt.Boolean_Literal(True)) 

1250 self.start.add_edge_to(gn_end) 

1251 

1252 ### 2: LHS is not true 

1253 self.start = current_start 

1254 self.attach_assumption(smt.Boolean_Negation(lhs_value)) 

1255 rhs_value, rhs_valid = self.tr_expression(n_expr.n_rhs) 

1256 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1257 self.attach_temp_declaration(n_expr, 

1258 sym_result, 

1259 rhs_value) 

1260 self.start.add_edge_to(gn_end) 

1261 

1262 # Join paths 

1263 self.start = gn_end 

1264 

1265 return sym_result, smt.Boolean_Literal(True) 

1266 

1267 def tr_core_equality_tuple_component(self, n_component, lhs, rhs): 

1268 assert isinstance(n_component, Composite_Component) 

1269 assert isinstance(lhs, smt.Expression) 

1270 assert isinstance(rhs, smt.Expression) 

1271 

1272 value_lhs = smt.Record_Access(lhs, 

1273 n_component.name + ".value") 

1274 value_rhs = smt.Record_Access(rhs, 

1275 n_component.name + ".value") 

1276 valid_equal = self.tr_core_equality(n_component.n_typ, 

1277 value_lhs, 

1278 value_rhs) 

1279 

1280 if not n_component.optional: 

1281 return valid_equal 

1282 

1283 valid_lhs = smt.Record_Access(lhs, 

1284 n_component.name + ".valid") 

1285 valid_rhs = smt.Record_Access(rhs, 

1286 n_component.name + ".valid") 

1287 

1288 return smt.Conjunction( 

1289 smt.Comparison("=", valid_lhs, valid_rhs), 

1290 smt.Implication(valid_lhs, valid_equal)) 

1291 

1292 def tr_core_equality(self, n_typ, lhs, rhs): 

1293 assert isinstance(n_typ, Type) 

1294 assert isinstance(lhs, smt.Expression) 

1295 assert isinstance(rhs, smt.Expression) 

1296 

1297 if isinstance(n_typ, Tuple_Type): 

1298 parts = [] 

1299 for n_component in n_typ.all_components(): 

1300 parts.append( 

1301 self.tr_core_equality_tuple_component(n_component, 

1302 lhs, 

1303 rhs)) 

1304 

1305 if len(parts) == 0: 1305 ↛ 1306line 1305 didn't jump to line 1306 because the condition on line 1305 was never true

1306 return smt.Boolean_Literal(True) 

1307 elif len(parts) == 1: 1307 ↛ 1308line 1307 didn't jump to line 1308 because the condition on line 1307 was never true

1308 return parts[0] 

1309 else: 

1310 result = smt.Conjunction(parts[0], parts[1]) 

1311 for part in parts[2:]: 

1312 result = smt.Conjunction(result, part) 

1313 return result 

1314 

1315 else: 

1316 return smt.Comparison("=", lhs, rhs) 

1317 

1318 def tr_op_equality(self, n_expr): 

1319 assert isinstance(n_expr, Binary_Expression) 

1320 assert n_expr.operator in (Binary_Operator.COMP_EQ, 

1321 Binary_Operator.COMP_NEQ) 

1322 

1323 lhs_value, lhs_valid = self.tr_expression(n_expr.n_lhs) 

1324 rhs_value, rhs_valid = self.tr_expression(n_expr.n_rhs) 

1325 

1326 if lhs_value is None: 

1327 comp_typ = n_expr.n_rhs.typ 

1328 else: 

1329 comp_typ = n_expr.n_lhs.typ 

1330 

1331 if lhs_valid.is_static_true() and rhs_valid.is_static_true(): 

1332 # Simplified form, this is just x == y 

1333 result = self.tr_core_equality(comp_typ, 

1334 lhs_value, 

1335 rhs_value) 

1336 

1337 elif lhs_valid.is_static_false() and rhs_valid.is_static_false(): 

1338 # This is null == null, so true 

1339 result = smt.Boolean_Literal(True) 

1340 

1341 elif lhs_value is None: 1341 ↛ 1343line 1341 didn't jump to line 1343 because the condition on line 1341 was never true

1342 # This is null == <expr>, true iff rhs is null 

1343 result = smt.Boolean_Negation(rhs_valid) 

1344 

1345 elif rhs_value is None: 

1346 # This is <expr> == null, true iff lhs is null 

1347 result = smt.Boolean_Negation(lhs_valid) 

1348 

1349 else: 

1350 # This is <expr> == <expr> without shortcuts 

1351 result = smt.Conjunction( 

1352 smt.Comparison("=", lhs_valid, rhs_valid), 

1353 smt.Implication(lhs_valid, 

1354 self.tr_core_equality(comp_typ, 

1355 lhs_value, 

1356 rhs_value))) 

1357 

1358 if n_expr.operator == Binary_Operator.COMP_NEQ: 

1359 result = smt.Boolean_Negation(result) 

1360 

1361 return self.create_return(n_expr, result) 

1362 

1363 def tr_quantified_expression(self, n_expr): 

1364 assert isinstance(n_expr, Quantified_Expression) 

1365 

1366 # Nested quantifiers are not supported yet 

1367 if self.functional: # pragma: no cover 

1368 self.flag_unsupported(n_expr, 

1369 "functional evaluation of quantifier") 

1370 

1371 # TRLC quantifier 

1372 # (forall x in arr_name => body) 

1373 # 

1374 # SMT quantifier 

1375 # (forall ((i Int)) 

1376 # (=> (and (>= i 0) (< i (seq.len arr_name))) 

1377 # (... (seq.nth arr_name i) ... ))) 

1378 # 

1379 # There is an alternative which is: 

1380 # (forall ((element ElementSort)) 

1381 # (=> (seq.contains arr_name (seq.unit element)) 

1382 # (... element ...) 

1383 # 

1384 # However it looks like for CVC5 at least this generates more 

1385 # unknown and less unsat if a check depends on the explicit 

1386 # value of some sequence member. 

1387 

1388 # Evaluate subject first and creat a null check 

1389 s_subject_value, s_subject_valid = \ 

1390 self.tr_name_reference(n_expr.n_source) 

1391 self.attach_validity_check(s_subject_valid, n_expr.n_source) 

1392 

1393 # Create validity checks for the body. We do this by creating 

1394 # a new branch and eliminating the quantifier; pretending it's 

1395 # a forall (since we want to show that for all evaluations 

1396 # it's valid). 

1397 current_start = self.start 

1398 self.attach_empty_assumption() 

1399 src_typ = n_expr.n_source.typ 

1400 assert isinstance(src_typ, Array_Type) 

1401 s_qe_index = smt.Constant(smt.BUILTIN_INTEGER, 

1402 self.new_temp_name()) 

1403 self.start.add_statement( 

1404 smt.Constant_Declaration( 

1405 symbol = s_qe_index, 

1406 comment = ("quantifier elimination (index) for %s at %s" % 

1407 (n_expr.to_string(), 

1408 n_expr.location.to_string())))) 

1409 self.start.add_statement( 

1410 smt.Assertion(smt.Comparison(">=", 

1411 s_qe_index, 

1412 smt.Integer_Literal(0)))) 

1413 self.start.add_statement( 

1414 smt.Assertion( 

1415 smt.Comparison("<", 

1416 s_qe_index, 

1417 smt.Sequence_Length(s_subject_value)))) 

1418 s_qe_sym = smt.Constant(self.tr_type(src_typ.element_type), 

1419 self.new_temp_name()) 

1420 self.start.add_statement( 

1421 smt.Constant_Declaration( 

1422 symbol = s_qe_sym, 

1423 value = smt.Sequence_Index(s_subject_value, s_qe_index), 

1424 comment = ("quantifier elimination (symbol) for %s at %s" % 

1425 (n_expr.to_string(), 

1426 n_expr.location.to_string())))) 

1427 self.qe_vars[n_expr.n_var] = s_qe_sym 

1428 

1429 _, b_valid = self.tr_expression(n_expr.n_expr) 

1430 self.attach_validity_check(b_valid, n_expr.n_expr) 

1431 

1432 self.start = current_start 

1433 del self.qe_vars[n_expr.n_var] 

1434 

1435 # We have now shown that any path in the quantifier cannot 

1436 # raise exception. Asserting the actual value of the 

1437 # quantifier is more awkward. 

1438 

1439 s_q_idx = smt.Bound_Variable(smt.BUILTIN_INTEGER, 

1440 self.new_temp_name()) 

1441 s_q_sym = smt.Sequence_Index(s_subject_value, s_q_idx) 

1442 self.bound_vars[n_expr.n_var] = s_q_sym 

1443 

1444 temp, self.functional = self.functional, True 

1445 b_value, _ = self.tr_expression(n_expr.n_expr) 

1446 self.functional = temp 

1447 

1448 bounds_expr = smt.Conjunction( 

1449 smt.Comparison(">=", 

1450 s_q_idx, 

1451 smt.Integer_Literal(0)), 

1452 smt.Comparison("<", 

1453 s_q_idx, 

1454 smt.Sequence_Length(s_subject_value))) 

1455 if n_expr.universal: 

1456 value = smt.Quantifier( 

1457 "forall", 

1458 [s_q_idx], 

1459 smt.Implication(bounds_expr, b_value)) 

1460 else: 

1461 value = smt.Quantifier( 

1462 "exists", 

1463 [s_q_idx], 

1464 smt.Conjunction(bounds_expr, b_value)) 

1465 

1466 return value, smt.Boolean_Literal(True) 

1467 

1468 def tr_field_access_expression(self, n_expr): 

1469 assert isinstance(n_expr, Field_Access_Expression) 

1470 

1471 if self.functional: # pragma: no cover 

1472 self.flag_unsupported(n_expr, 

1473 "functional evaluation of field access") 

1474 

1475 prefix_value, prefix_valid = self.tr_expression(n_expr.n_prefix) 

1476 prefix_typ = n_expr.n_prefix.typ 

1477 self.attach_validity_check(prefix_valid, n_expr.n_prefix) 

1478 

1479 if isinstance(prefix_typ, Tuple_Type): 

1480 field_value = smt.Record_Access(prefix_value, 

1481 n_expr.n_field.name + ".value") 

1482 if n_expr.n_field.optional: 

1483 field_valid = smt.Record_Access(prefix_value, 

1484 n_expr.n_field.name + ".valid") 

1485 else: 

1486 field_valid = smt.Boolean_Literal(True) 

1487 

1488 elif isinstance(prefix_typ, Record_Type): 

1489 # We need a sort for the record instance + a UF to convert 

1490 # the int values into instances of this sort. 

1491 if prefix_typ in self.records: 

1492 record_sort = self.records[prefix_typ] 

1493 to_record_uf = self.uf_records[prefix_typ] 

1494 else: 

1495 record_sort = smt.Record(prefix_typ.n_package.name + 

1496 "." + prefix_typ.name) 

1497 for n_component in prefix_typ.all_components(): 

1498 record_sort.add_component(n_component.name + ".value", 

1499 self.tr_type(n_component.n_typ)) 

1500 if n_component.optional: 

1501 record_sort.add_component(n_component.name + ".valid", 

1502 smt.BUILTIN_BOOLEAN) 

1503 self.records[prefix_typ] = record_sort 

1504 self.preamble.add_statement( 

1505 smt.Record_Declaration( 

1506 record_sort, 

1507 "record %s from %s" % ( 

1508 prefix_typ.name, 

1509 prefix_typ.location.to_string()))) 

1510 

1511 to_record_uf = smt.Function( 

1512 "access.%s.%s" % 

1513 (prefix_typ.n_package.name, prefix_typ.name), 

1514 record_sort, 

1515 smt.Bound_Variable(smt.BUILTIN_INTEGER, "ref")) 

1516 self.preamble.add_statement( 

1517 smt.Function_Declaration(to_record_uf)) 

1518 

1519 self.uf_records[prefix_typ] = to_record_uf 

1520 

1521 # We can now apply the magic int to the UF to get a record 

1522 # value 

1523 dereference = smt.Function_Application(to_record_uf, prefix_value) 

1524 

1525 # We can now perform the access on the record value 

1526 field_value = smt.Record_Access(dereference, 

1527 n_expr.n_field.name + ".value") 

1528 if n_expr.n_field.optional: 

1529 field_valid = smt.Record_Access(dereference, 

1530 n_expr.n_field.name + ".valid") 

1531 else: 

1532 field_valid = smt.Boolean_Literal(True) 

1533 

1534 else: 

1535 self.mh.ice_loc(n_expr.n_prefix.location, 

1536 "unexpected type %s as prefix of field access" % 

1537 n_expr.n_prefix.typ.__class__.__name__) 

1538 

1539 # pylint: disable=possibly-used-before-assignment 

1540 return field_value, field_valid