Coverage for trlc/vcg.py: 97%

750 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-03-27 00:52 +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 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.arrays = {} 

107 self.bound_vars = {} 

108 self.qe_vars = {} 

109 self.tuple_base = {} 

110 

111 self.uf_matches = None 

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

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

114 # string theory being used. 

115 

116 self.functional = False 

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

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

119 # expresions and return them. 

120 

121 self.emit_checks = True 

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

123 

124 @staticmethod 

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

126 assert isinstance(node, Node) 

127 raise Unsupported(node, text) 

128 

129 def new_temp_name(self): 

130 self.tmp_id += 1 

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

132 

133 def get_uf_matches(self): 

134 if self.uf_matches is None: 

135 self.uf_matches = \ 

136 smt.Function("trlc.matches", 

137 smt.BUILTIN_BOOLEAN, 

138 smt.Bound_Variable(smt.BUILTIN_STRING, 

139 "subject"), 

140 smt.Bound_Variable(smt.BUILTIN_STRING, 

141 "regex")) 

142 

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

144 # will deal with regex properly). 

145 self.preamble.add_statement( 

146 smt.Function_Declaration(self.uf_matches)) 

147 

148 return self.uf_matches 

149 

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

151 assert isinstance(node, Expression) 

152 assert isinstance(s_value, smt.Expression) 

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

154 

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

156 s_valid = smt.Boolean_Literal(True) 

157 

158 if self.functional: 

159 return s_value, s_valid 

160 

161 else: 

162 sym_result = smt.Constant(s_value.sort, 

163 self.new_temp_name()) 

164 self.attach_temp_declaration(node, sym_result, s_value) 

165 

166 return sym_result, s_valid 

167 

168 def attach_validity_check(self, bool_expr, origin): 

169 assert isinstance(bool_expr, smt.Expression) 

170 assert bool_expr.sort is smt.BUILTIN_BOOLEAN 

171 assert isinstance(origin, Expression) 

172 assert not self.functional 

173 

174 if not self.emit_checks: 

175 return 

176 

177 # Attach new graph node advance start 

178 if not bool_expr.is_static_true(): 

179 gn_check = graph.Check(self.graph) 

180 gn_check.add_goal(bool_expr, 

181 Feedback(origin, 

182 "expression could be null", 

183 "evaluation-of-null"), 

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

185 self.start.add_edge_to(gn_check) 

186 self.start = gn_check 

187 

188 def attach_int_division_check(self, int_expr, origin): 

189 assert isinstance(int_expr, smt.Expression) 

190 assert int_expr.sort is smt.BUILTIN_INTEGER 

191 assert isinstance(origin, Expression) 

192 assert not self.functional 

193 

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

195 return 

196 

197 # Attach new graph node advance start 

198 gn_check = graph.Check(self.graph) 

199 gn_check.add_goal( 

200 smt.Boolean_Negation( 

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

202 Feedback(origin, 

203 "divisor could be 0", 

204 "div-by-zero"), 

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

206 self.start.add_edge_to(gn_check) 

207 self.start = gn_check 

208 

209 def attach_real_division_check(self, real_expr, origin): 

210 assert isinstance(real_expr, smt.Expression) 

211 assert real_expr.sort is smt.BUILTIN_REAL 

212 assert isinstance(origin, Expression) 

213 assert not self.functional 

214 

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

216 return 

217 

218 # Attach new graph node advance start 

219 gn_check = graph.Check(self.graph) 

220 gn_check.add_goal( 

221 smt.Boolean_Negation( 

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

223 Feedback(origin, 

224 "divisor could be 0.0", 

225 "div-by-zero"), 

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

227 self.start.add_edge_to(gn_check) 

228 self.start = gn_check 

229 

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

231 assert isinstance(seq_expr, smt.Expression) 

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

233 assert isinstance(index_expr, smt.Expression) 

234 assert index_expr.sort is smt.BUILTIN_INTEGER 

235 assert isinstance(origin, Binary_Expression) 

236 assert origin.operator == Binary_Operator.INDEX 

237 assert not self.functional 

238 

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

240 return 

241 

242 # Attach new graph node advance start 

243 gn_check = graph.Check(self.graph) 

244 gn_check.add_goal( 

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

246 Feedback(origin, 

247 "array index could be less than 0", 

248 "array-index"), 

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

250 gn_check.add_goal( 

251 smt.Comparison("<", 

252 index_expr, 

253 smt.Sequence_Length(seq_expr)), 

254 Feedback(origin, 

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

256 origin.n_lhs.to_string(), 

257 "array-index"), 

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

259 

260 self.start.add_edge_to(gn_check) 

261 self.start = gn_check 

262 

263 def attach_feasability_check(self, bool_expr, origin): 

264 assert isinstance(bool_expr, smt.Expression) 

265 assert bool_expr.sort is smt.BUILTIN_BOOLEAN 

266 assert isinstance(origin, Expression) 

267 assert not self.functional 

268 

269 if not self.emit_checks: 

270 return 

271 

272 # Attach new graph node advance start 

273 gn_check = graph.Check(self.graph) 

274 gn_check.add_goal(bool_expr, 

275 Feedback(origin, 

276 "expression is always true", 

277 "always-true", 

278 expect_unsat = False), 

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

280 self.start.add_edge_to(gn_check) 

281 

282 def attach_assumption(self, bool_expr): 

283 assert isinstance(bool_expr, smt.Expression) 

284 assert bool_expr.sort is smt.BUILTIN_BOOLEAN 

285 assert not self.functional 

286 

287 # Attach new graph node advance start 

288 gn_ass = graph.Assumption(self.graph) 

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

290 self.start.add_edge_to(gn_ass) 

291 self.start = gn_ass 

292 

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

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

295 assert isinstance(sym, smt.Constant) 

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

297 assert not self.functional 

298 

299 # Attach new graph node advance start 

300 gn_decl = graph.Assumption(self.graph) 

301 gn_decl.add_statement( 

302 smt.Constant_Declaration( 

303 symbol = sym, 

304 value = value, 

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

306 node.location.to_string()), 

307 relevant = False)) 

308 self.start.add_edge_to(gn_decl) 

309 self.start = gn_decl 

310 

311 def attach_empty_assumption(self): 

312 assert not self.functional 

313 

314 # Attach new graph node advance start 

315 gn_decl = graph.Assumption(self.graph) 

316 self.start.add_edge_to(gn_decl) 

317 self.start = gn_decl 

318 

319 def analyze(self): 

320 try: 

321 self.checks_on_composite_type(self.n_ctyp) 

322 except Unsupported as exc: # pragma: no cover 

323 self.mh.warning(exc.location, 

324 exc.message) 

325 

326 def checks_on_composite_type(self, n_ctyp): 

327 assert isinstance(n_ctyp, Composite_Type) 

328 

329 # Create node for global declarations 

330 gn_locals = graph.Assumption(self.graph) 

331 self.start.add_edge_to(gn_locals) 

332 self.start = gn_locals 

333 self.preamble = gn_locals 

334 

335 # Create local variables 

336 for n_component in n_ctyp.all_components(): 

337 self.tr_component_decl(n_component, self.start) 

338 

339 # Create paths for checks 

340 for n_check in n_ctyp.iter_checks(): 

341 current_start = self.start 

342 self.tr_check(n_check) 

343 

344 # Only fatal checks contribute to the total knowledge 

345 if n_check.severity != "fatal": 

346 self.start = current_start 

347 

348 # Emit debug graph 

349 if self.debug: # pragma: no cover 

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

351 input = self.graph.debug_render_dot(), 

352 check = True, 

353 encoding = "UTF-8") 

354 

355 # Generate VCs 

356 self.vcg.generate() 

357 

358 # Solve VCs and provide feedback 

359 nok_feasibility_checks = [] 

360 ok_feasibility_checks = set() 

361 nok_validity_checks = set() 

362 

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

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

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

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

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

368 

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

370 # again on a different path 

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

372 vc["feedback"] in nok_validity_checks: 

373 continue 

374 

375 if self.use_api: 

376 solver = CVC5_Solver() 

377 else: 

378 solver = CVC5_File_Solver(self.cvc5_bin) 

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

380 solver.set_solver_option(name, value) 

381 

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

383 

384 message = vc["feedback"].message 

385 if self.debug: # pragma: no cover 

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

387 

388 if vc["feedback"].expect_unsat: 

389 if status != "unsat": 

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

391 message, 

392 vc["feedback"].kind, 

393 self.create_counterexample(status, 

394 values)) 

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

396 else: 

397 if status == "unsat": 

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

399 else: 

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

401 

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

403 # consistent 

404 for feedback in nok_feasibility_checks: 

405 if feedback not in ok_feasibility_checks: 

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

407 feedback.message, 

408 feedback.kind) 

409 ok_feasibility_checks.add(feedback) 

410 

411 def create_counterexample(self, status, values): 

412 rv = [ 

413 "example %s triggering error:" % 

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

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

416 ] 

417 

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

419 id_value = self.tr_component_value_name(n_component) 

420 id_valid = self.tr_component_valid_name(n_component) 

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

422 id_valid not in values): 

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

424 elif values.get(id_valid): 

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

426 (n_component.name, 

427 self.value_to_trlc(n_component.n_typ, 

428 values[id_value]))) 

429 else: 

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

431 

432 rv.append(" }") 

433 if status == "unknown": 

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

435 return "\n".join(rv) 

436 

437 def fraction_to_decimal_string(self, num, den): 

438 assert isinstance(num, int) 

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

440 

441 tmp = den 

442 if tmp > 2: 

443 while tmp > 1: 

444 if tmp % 2 == 0: 

445 tmp = tmp // 2 

446 elif tmp % 5 == 0: 

447 tmp = tmp // 5 

448 else: 

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

450 

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

452 

453 i = abs(num) % den 

454 j = den 

455 

456 if i > 0: 

457 rv += "." 

458 while i > 0: 

459 i *= 10 

460 rv += str(i // j) 

461 i = i % j 

462 else: 

463 rv += ".0" 

464 

465 if num < 0: 

466 return "-" + rv 

467 else: 

468 return rv 

469 

470 def value_to_trlc(self, n_typ, value): 

471 assert isinstance(n_typ, Type) 

472 

473 if isinstance(n_typ, Builtin_Integer): 

474 return str(value) 

475 

476 elif isinstance(n_typ, Builtin_Decimal): 

477 if isinstance(value, Fraction): 

478 num, den = value.as_integer_ratio() 

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

480 return self.fraction_to_decimal_string(num, den) 

481 else: 

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

483 else: 

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

485 

486 elif isinstance(n_typ, Builtin_Boolean): 

487 return "true" if value else "false" 

488 

489 elif isinstance(n_typ, Enumeration_Type): 

490 return n_typ.name + "." + value 

491 

492 elif isinstance(n_typ, Builtin_String): 

493 if "\n" in value: 

494 return "'''%s'''" % value 

495 else: 

496 return '"%s"' % value 

497 

498 elif isinstance(n_typ, Record_Type): 

499 if value < 0: 

500 instance_id = value * -2 - 1 

501 else: 

502 instance_id = value * 2 

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

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

505 else: 

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

507 n_typ.name, 

508 instance_id) 

509 

510 elif isinstance(n_typ, Tuple_Type): 

511 parts = [] 

512 for n_item in n_typ.iter_sequence(): 

513 if isinstance(n_item, Composite_Component): 

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

515 parts.pop() 

516 break 

517 parts.append( 

518 self.value_to_trlc(n_item.n_typ, 

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

520 

521 else: 

522 assert isinstance(n_item, Separator) 

523 sep_text = { 

524 "AT" : "@", 

525 "COLON" : ":", 

526 "SEMICOLON" : ";" 

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

528 parts.append(sep_text) 

529 

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

531 return "".join(parts) 

532 else: 

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

534 

535 elif isinstance(n_typ, Array_Type): 

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

537 item) 

538 for item in value) 

539 

540 else: # pragma: no cover 

541 self.flag_unsupported(n_typ, 

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

543 

544 def tr_component_value_name(self, n_component): 

545 return n_component.member_of.fully_qualified_name() + \ 

546 "." + n_component.name + ".value" 

547 

548 def tr_component_valid_name(self, n_component): 

549 return n_component.member_of.fully_qualified_name() + \ 

550 "." + n_component.name + ".valid" 

551 

552 def emit_tuple_constraints(self, n_tuple, s_sym): 

553 assert isinstance(n_tuple, Tuple_Type) 

554 assert isinstance(s_sym, smt.Constant) 

555 

556 old_functional, self.functional = self.functional, True 

557 self.tuple_base[n_tuple] = s_sym 

558 

559 constraints = [] 

560 

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

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

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

564 # record. 

565 

566 for n_check in n_tuple.iter_checks(): 

567 if n_check.severity == "warning": 

568 continue 

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

570 # truth here. 

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

572 constraints.append(c_value) 

573 

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

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

576 

577 components = n_tuple.all_components() 

578 for i, component in enumerate(components): 

579 if component.optional: 

580 condition = smt.Boolean_Negation( 

581 smt.Record_Access(s_sym, 

582 component.name + ".valid")) 

583 consequences = [ 

584 smt.Boolean_Negation( 

585 smt.Record_Access(s_sym, 

586 c.name + ".valid")) 

587 for c in components[i + 1:] 

588 ] 

589 if len(consequences) == 0: 

590 break 

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

592 consequence = consequences[0] 

593 else: 

594 consequence = smt.Conjunction(*consequences) 

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

596 

597 del self.tuple_base[n_tuple] 

598 self.functional = old_functional 

599 

600 for cons in constraints: 

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

602 

603 def tr_component_decl(self, n_component, gn_locals): 

604 assert isinstance(n_component, Composite_Component) 

605 assert isinstance(gn_locals, graph.Assumption) 

606 

607 if isinstance(self.n_ctyp, Record_Type): 

608 frozen = self.n_ctyp.is_frozen(n_component) 

609 else: 

610 frozen = False 

611 

612 id_value = self.tr_component_value_name(n_component) 

613 s_sort = self.tr_type(n_component.n_typ) 

614 s_sym = smt.Constant(s_sort, id_value) 

615 if frozen: 

616 old_functional, self.functional = self.functional, True 

617 s_val, _ = self.tr_expression( 

618 self.n_ctyp.get_freezing_expression(n_component)) 

619 self.functional = old_functional 

620 else: 

621 s_val = None 

622 s_decl = smt.Constant_Declaration( 

623 symbol = s_sym, 

624 value = s_val, 

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

626 n_component.name, 

627 n_component.location.to_string()), 

628 relevant = True) 

629 gn_locals.add_statement(s_decl) 

630 self.constants[id_value] = s_sym 

631 

632 if isinstance(n_component.n_typ, Tuple_Type): 

633 self.emit_tuple_constraints(n_component.n_typ, s_sym) 

634 

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

636 # length 

637 if isinstance(n_component.n_typ, Array_Type): 

638 if n_component.n_typ.lower_bound > 0: 

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

640 gn_locals.add_statement( 

641 smt.Assertion( 

642 smt.Comparison(">=", 

643 smt.Sequence_Length(s_sym), 

644 s_lower))) 

645 

646 if n_component.n_typ.upper_bound is not None: 

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

648 gn_locals.add_statement( 

649 smt.Assertion( 

650 smt.Comparison("<=", 

651 smt.Sequence_Length(s_sym), 

652 s_upper))) 

653 

654 id_valid = self.tr_component_valid_name(n_component) 

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

656 s_val = (None 

657 if n_component.optional and not frozen 

658 else smt.Boolean_Literal(True)) 

659 s_decl = smt.Constant_Declaration( 

660 symbol = s_sym, 

661 value = s_val, 

662 relevant = True) 

663 gn_locals.add_statement(s_decl) 

664 self.constants[id_valid] = s_sym 

665 

666 def tr_type(self, n_type): 

667 assert isinstance(n_type, Type) 

668 

669 if isinstance(n_type, Builtin_Boolean): 

670 return smt.BUILTIN_BOOLEAN 

671 

672 elif isinstance(n_type, Builtin_Integer): 

673 return smt.BUILTIN_INTEGER 

674 

675 elif isinstance(n_type, Builtin_Decimal): 

676 return smt.BUILTIN_REAL 

677 

678 elif isinstance(n_type, Builtin_String): 

679 return smt.BUILTIN_STRING 

680 

681 elif isinstance(n_type, Enumeration_Type): 

682 if n_type not in self.enumerations: 

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

684 "." + n_type.name) 

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

686 s_sort.add_literal(n_lit.name) 

687 self.enumerations[n_type] = s_sort 

688 self.start.add_statement( 

689 smt.Enumeration_Declaration( 

690 s_sort, 

691 "enumeration %s from %s" % ( 

692 n_type.name, 

693 n_type.location.to_string()))) 

694 return self.enumerations[n_type] 

695 

696 elif isinstance(n_type, Tuple_Type): 

697 if n_type not in self.tuples: 

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

699 "." + n_type.name) 

700 for n_component in n_type.all_components(): 

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

702 self.tr_type(n_component.n_typ)) 

703 if n_component.optional: 

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

705 smt.BUILTIN_BOOLEAN) 

706 self.tuples[n_type] = s_sort 

707 self.start.add_statement( 

708 smt.Record_Declaration( 

709 s_sort, 

710 "tuple %s from %s" % ( 

711 n_type.name, 

712 n_type.location.to_string()))) 

713 

714 return self.tuples[n_type] 

715 

716 elif isinstance(n_type, Array_Type): 

717 if n_type not in self.arrays: 717 ↛ 722line 717 didn't jump to line 722 because the condition on line 717 was always true

718 s_element_sort = self.tr_type(n_type.element_type) 

719 s_sequence = smt.Sequence_Sort(s_element_sort) 

720 self.arrays[n_type] = s_sequence 

721 

722 return self.arrays[n_type] 

723 

724 elif isinstance(n_type, Record_Type): 

725 # Record references are modelled as a free integer, since 

726 # we can't really _do_ anything with them. We just need a 

727 # variable with infinite range so we can generate 

728 # arbitrary fictional record names 

729 return smt.BUILTIN_INTEGER 

730 

731 else: # pragma: no cover 

732 self.flag_unsupported(n_type) 

733 

734 def tr_check(self, n_check): 

735 assert isinstance(n_check, Check) 

736 

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

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

739 # again, because if a check would failt it would already have 

740 # failed. 

741 if n_check.n_type is not self.n_ctyp: 

742 old_emit, self.emit_checks = self.emit_checks, False 

743 

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

745 self.attach_validity_check(valid, n_check.n_expr) 

746 self.attach_feasability_check(value, n_check.n_expr) 

747 self.attach_assumption(value) 

748 

749 if n_check.n_type is not self.n_ctyp: 

750 self.emit_checks = old_emit 

751 

752 def tr_expression(self, n_expr): 

753 value = None 

754 

755 if isinstance(n_expr, Name_Reference): 

756 return self.tr_name_reference(n_expr) 

757 

758 elif isinstance(n_expr, Unary_Expression): 

759 return self.tr_unary_expression(n_expr) 

760 

761 elif isinstance(n_expr, Binary_Expression): 

762 return self.tr_binary_expression(n_expr) 

763 

764 elif isinstance(n_expr, Range_Test): 

765 return self.tr_range_test(n_expr) 

766 

767 elif isinstance(n_expr, OneOf_Expression): 

768 return self.tr_oneof_test(n_expr) 

769 

770 elif isinstance(n_expr, Conditional_Expression): 

771 if self.functional: 

772 return self.tr_conditional_expression_functional(n_expr) 

773 else: 

774 return self.tr_conditional_expression(n_expr) 

775 

776 elif isinstance(n_expr, Null_Literal): 

777 return None, smt.Boolean_Literal(False) 

778 

779 elif isinstance(n_expr, Boolean_Literal): 

780 value = smt.Boolean_Literal(n_expr.value) 

781 

782 elif isinstance(n_expr, Integer_Literal): 

783 value = smt.Integer_Literal(n_expr.value) 

784 

785 elif isinstance(n_expr, Decimal_Literal): 

786 value = smt.Real_Literal(n_expr.value) 

787 

788 elif isinstance(n_expr, Enumeration_Literal): 

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

790 n_expr.value.name) 

791 

792 elif isinstance(n_expr, String_Literal): 

793 value = smt.String_Literal(n_expr.value) 

794 

795 elif isinstance(n_expr, Quantified_Expression): 

796 return self.tr_quantified_expression(n_expr) 

797 

798 elif isinstance(n_expr, Field_Access_Expression): 

799 return self.tr_field_access_expression(n_expr) 

800 

801 else: # pragma: no cover 

802 self.flag_unsupported(n_expr) 

803 

804 return value, smt.Boolean_Literal(True) 

805 

806 def tr_name_reference(self, n_ref): 

807 assert isinstance(n_ref, Name_Reference) 

808 

809 if isinstance(n_ref.entity, Composite_Component): 

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

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

812 if n_ref.entity.optional: 

813 s_valid = smt.Record_Access(sym, 

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

815 else: 

816 s_valid = smt.Boolean_Literal(True) 

817 s_value = smt.Record_Access(sym, 

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

819 return s_value, s_valid 

820 

821 else: 

822 id_value = self.tr_component_value_name(n_ref.entity) 

823 id_valid = self.tr_component_valid_name(n_ref.entity) 

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

825 

826 else: 

827 assert isinstance(n_ref.entity, Quantified_Variable) 

828 if n_ref.entity in self.qe_vars: 

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

830 else: 

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

832 

833 def tr_unary_expression(self, n_expr): 

834 assert isinstance(n_expr, Unary_Expression) 

835 

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

837 if not self.functional: 

838 self.attach_validity_check(operand_valid, n_expr.n_operand) 

839 

840 sym_value = None 

841 

842 if n_expr.operator == Unary_Operator.MINUS: 

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

844 sym_value = smt.Unary_Int_Arithmetic_Op("-", 

845 operand_value) 

846 else: 

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

848 sym_value = smt.Unary_Real_Arithmetic_Op("-", 

849 operand_value) 

850 

851 elif n_expr.operator == Unary_Operator.PLUS: 

852 sym_value = operand_value 

853 

854 elif n_expr.operator == Unary_Operator.LOGICAL_NOT: 

855 sym_value = smt.Boolean_Negation(operand_value) 

856 

857 elif n_expr.operator == Unary_Operator.ABSOLUTE_VALUE: 

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

859 sym_value = smt.Unary_Int_Arithmetic_Op("abs", 

860 operand_value) 

861 

862 else: 

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

864 sym_value = smt.Unary_Real_Arithmetic_Op("abs", 

865 operand_value) 

866 

867 elif n_expr.operator == Unary_Operator.STRING_LENGTH: 

868 sym_value = smt.String_Length(operand_value) 

869 

870 elif n_expr.operator == Unary_Operator.ARRAY_LENGTH: 

871 sym_value = smt.Sequence_Length(operand_value) 

872 

873 elif n_expr.operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

874 sym_value = smt.Conversion_To_Real(operand_value) 

875 

876 elif n_expr.operator == Unary_Operator.CONVERSION_TO_INT: 

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

878 

879 else: 

880 self.mh.ice_loc(n_expr, 

881 "unexpected unary operator %s" % 

882 n_expr.operator.name) 

883 

884 return self.create_return(n_expr, sym_value) 

885 

886 def tr_binary_expression(self, n_expr): 

887 assert isinstance(n_expr, Binary_Expression) 

888 

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

890 # deal with them first and then exit. 

891 if n_expr.operator in (Binary_Operator.COMP_EQ, 

892 Binary_Operator.COMP_NEQ): 

893 return self.tr_op_equality(n_expr) 

894 

895 elif n_expr.operator == Binary_Operator.LOGICAL_IMPLIES: 

896 return self.tr_op_implication(n_expr) 

897 

898 elif n_expr.operator == Binary_Operator.LOGICAL_AND: 

899 return self.tr_op_and(n_expr) 

900 

901 elif n_expr.operator == Binary_Operator.LOGICAL_OR: 

902 return self.tr_op_or(n_expr) 

903 

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

905 # obtain the values of both sides now. 

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

907 if not self.functional: 

908 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

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

910 if not self.functional: 

911 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

912 sym_value = None 

913 

914 if n_expr.operator == Binary_Operator.LOGICAL_XOR: 

915 sym_value = smt.Exclusive_Disjunction(lhs_value, rhs_value) 

916 

917 elif n_expr.operator in (Binary_Operator.PLUS, 

918 Binary_Operator.MINUS, 

919 Binary_Operator.TIMES, 

920 Binary_Operator.DIVIDE, 

921 Binary_Operator.REMAINDER): 

922 

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

924 assert n_expr.operator == Binary_Operator.PLUS 

925 sym_value = smt.String_Concatenation(lhs_value, rhs_value) 

926 

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

928 if n_expr.operator in (Binary_Operator.DIVIDE, 

929 Binary_Operator.REMAINDER): 

930 self.attach_int_division_check(rhs_value, n_expr) 

931 

932 smt_op = { 

933 Binary_Operator.PLUS : "+", 

934 Binary_Operator.MINUS : "-", 

935 Binary_Operator.TIMES : "*", 

936 Binary_Operator.DIVIDE : "floor_div", 

937 Binary_Operator.REMAINDER : "ada_remainder", 

938 }[n_expr.operator] 

939 

940 sym_value = smt.Binary_Int_Arithmetic_Op(smt_op, 

941 lhs_value, 

942 rhs_value) 

943 

944 else: 

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

946 if n_expr.operator == Binary_Operator.DIVIDE: 

947 self.attach_real_division_check(rhs_value, n_expr) 

948 

949 smt_op = { 

950 Binary_Operator.PLUS : "+", 

951 Binary_Operator.MINUS : "-", 

952 Binary_Operator.TIMES : "*", 

953 Binary_Operator.DIVIDE : "/", 

954 }[n_expr.operator] 

955 

956 sym_value = smt.Binary_Real_Arithmetic_Op(smt_op, 

957 lhs_value, 

958 rhs_value) 

959 

960 elif n_expr.operator in (Binary_Operator.COMP_LT, 

961 Binary_Operator.COMP_LEQ, 

962 Binary_Operator.COMP_GT, 

963 Binary_Operator.COMP_GEQ): 

964 smt_op = { 

965 Binary_Operator.COMP_LT : "<", 

966 Binary_Operator.COMP_LEQ : "<=", 

967 Binary_Operator.COMP_GT : ">", 

968 Binary_Operator.COMP_GEQ : ">=", 

969 }[n_expr.operator] 

970 

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

972 

973 elif n_expr.operator in (Binary_Operator.STRING_CONTAINS, 

974 Binary_Operator.STRING_STARTSWITH, 

975 Binary_Operator.STRING_ENDSWITH): 

976 

977 smt_op = { 

978 Binary_Operator.STRING_CONTAINS : "contains", 

979 Binary_Operator.STRING_STARTSWITH : "prefixof", 

980 Binary_Operator.STRING_ENDSWITH : "suffixof" 

981 } 

982 

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

984 # other way around than in TRLC. 

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

986 rhs_value, 

987 lhs_value) 

988 

989 elif n_expr.operator == Binary_Operator.STRING_REGEX: 

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

991 assert isinstance(rhs_evaluation, str) 

992 

993 sym_value = smt.Function_Application( 

994 self.get_uf_matches(), 

995 lhs_value, 

996 smt.String_Literal(rhs_evaluation)) 

997 

998 elif n_expr.operator == Binary_Operator.INDEX: 

999 self.attach_index_check(lhs_value, rhs_value, n_expr) 

1000 sym_value = smt.Sequence_Index(lhs_value, rhs_value) 

1001 

1002 elif n_expr.operator == Binary_Operator.ARRAY_CONTAINS: 

1003 sym_value = smt.Sequence_Contains(rhs_value, lhs_value) 

1004 

1005 elif n_expr.operator == Binary_Operator.POWER: 

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

1007 # integer 

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

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

1010 

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

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

1013 sym_value = smt.Integer_Literal(1) 

1014 else: 

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

1016 sym_value = smt.Real_Literal(1) 

1017 

1018 else: 

1019 sym_value = lhs_value 

1020 for _ in range(1, static_value): 

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

1022 sym_value = smt.Binary_Int_Arithmetic_Op("*", 

1023 sym_value, 

1024 lhs_value) 

1025 else: 

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

1027 sym_value = smt.Binary_Real_Arithmetic_Op("*", 

1028 sym_value, 

1029 lhs_value) 

1030 

1031 else: # pragma: no cover 

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

1033 

1034 return self.create_return(n_expr, sym_value) 

1035 

1036 def tr_range_test(self, n_expr): 

1037 assert isinstance(n_expr, Range_Test) 

1038 

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

1040 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

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

1042 self.attach_validity_check(lower_valid, n_expr.n_lower) 

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

1044 self.attach_validity_check(upper_valid, n_expr.n_upper) 

1045 

1046 sym_value = smt.Conjunction( 

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

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

1049 

1050 return self.create_return(n_expr, sym_value) 

1051 

1052 def tr_oneof_test(self, n_expr): 

1053 assert isinstance(n_expr, OneOf_Expression) 

1054 

1055 choices = [] 

1056 for n_choice in n_expr.choices: 

1057 c_value, c_valid = self.tr_expression(n_choice) 

1058 self.attach_validity_check(c_valid, n_choice) 

1059 choices.append(c_value) 

1060 

1061 negated_choices = [smt.Boolean_Negation(c) 

1062 for c in choices] 

1063 

1064 # pylint: disable=consider-using-enumerate 

1065 

1066 if len(choices) == 1: 

1067 result = choices[0] 

1068 elif len(choices) == 2: 

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

1070 else: 

1071 assert len(choices) >= 3 

1072 values = [] 

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

1074 sequence = [] 

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

1076 if other_id == choice_id: 

1077 sequence.append(choices[other_id]) 

1078 else: 

1079 sequence.append(negated_choices[other_id]) 

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

1081 result = smt.Disjunction(*values) 

1082 

1083 return self.create_return(n_expr, result) 

1084 

1085 def tr_conditional_expression_functional(self, n_expr): 

1086 assert isinstance(n_expr, Conditional_Expression) 

1087 

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

1089 for n_action in reversed(n_expr.actions): 

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

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

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

1093 

1094 return self.create_return(n_expr, s_result) 

1095 

1096 def tr_conditional_expression(self, n_expr): 

1097 assert isinstance(n_expr, Conditional_Expression) 

1098 assert not self.functional 

1099 

1100 gn_end = graph.Node(self.graph) 

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

1102 self.new_temp_name()) 

1103 

1104 for n_action in n_expr.actions: 

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

1106 self.attach_validity_check(test_valid, n_action.n_cond) 

1107 current_start = self.start 

1108 

1109 # Create path where action is true 

1110 self.attach_assumption(test_value) 

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

1112 self.attach_validity_check(res_valid, n_action.n_expr) 

1113 self.attach_temp_declaration(n_action, 

1114 sym_result, 

1115 res_value) 

1116 self.start.add_edge_to(gn_end) 

1117 

1118 # Reset to test and proceed with the other actions 

1119 self.start = current_start 

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

1121 

1122 # Finally execute the else part 

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

1124 self.attach_validity_check(res_valid, n_expr.else_expr) 

1125 self.attach_temp_declaration(n_expr, 

1126 sym_result, 

1127 res_value) 

1128 self.start.add_edge_to(gn_end) 

1129 

1130 # And join 

1131 self.start = gn_end 

1132 return sym_result, smt.Boolean_Literal(True) 

1133 

1134 def tr_op_implication(self, n_expr): 

1135 assert isinstance(n_expr, Binary_Expression) 

1136 assert n_expr.operator == Binary_Operator.LOGICAL_IMPLIES 

1137 

1138 if self.functional: 

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

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

1141 return self.create_return(n_expr, 

1142 smt.Implication(lhs_value, rhs_value)) 

1143 

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

1145 # Emit VC for validity 

1146 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1147 

1148 # Split into two paths. 

1149 current_start = self.start 

1150 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, 

1151 self.new_temp_name()) 

1152 gn_end = graph.Node(self.graph) 

1153 

1154 ### 1: Implication is not valid 

1155 self.start = current_start 

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

1157 self.attach_temp_declaration(n_expr, 

1158 sym_result, 

1159 smt.Boolean_Literal(True)) 

1160 self.start.add_edge_to(gn_end) 

1161 

1162 ### 2: Implication is valid. 

1163 self.start = current_start 

1164 self.attach_assumption(lhs_value) 

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

1166 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1167 self.attach_temp_declaration(n_expr, 

1168 sym_result, 

1169 rhs_value) 

1170 self.start.add_edge_to(gn_end) 

1171 

1172 # Join paths 

1173 self.start = gn_end 

1174 

1175 return sym_result, smt.Boolean_Literal(True) 

1176 

1177 def tr_op_and(self, n_expr): 

1178 assert isinstance(n_expr, Binary_Expression) 

1179 assert n_expr.operator == Binary_Operator.LOGICAL_AND 

1180 

1181 if self.functional: 

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

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

1184 return self.create_return(n_expr, 

1185 smt.Conjunction(lhs_value, rhs_value)) 

1186 

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

1188 # Emit VC for validity 

1189 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1190 

1191 # Split into two paths. 

1192 current_start = self.start 

1193 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, 

1194 self.new_temp_name()) 

1195 gn_end = graph.Node(self.graph) 

1196 

1197 ### 1: LHS is not true 

1198 self.start = current_start 

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

1200 self.attach_temp_declaration(n_expr, 

1201 sym_result, 

1202 smt.Boolean_Literal(False)) 

1203 self.start.add_edge_to(gn_end) 

1204 

1205 ### 2: LHS is true 

1206 self.start = current_start 

1207 self.attach_assumption(lhs_value) 

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

1209 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1210 self.attach_temp_declaration(n_expr, 

1211 sym_result, 

1212 rhs_value) 

1213 self.start.add_edge_to(gn_end) 

1214 

1215 # Join paths 

1216 self.start = gn_end 

1217 

1218 return sym_result, smt.Boolean_Literal(True) 

1219 

1220 def tr_op_or(self, n_expr): 

1221 assert isinstance(n_expr, Binary_Expression) 

1222 assert n_expr.operator == Binary_Operator.LOGICAL_OR 

1223 

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

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

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

1227 return self.create_return(n_expr, 

1228 smt.Disjunction(lhs_value, rhs_value)) 

1229 

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

1231 # Emit VC for validity 

1232 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1233 

1234 # Split into two paths. 

1235 current_start = self.start 

1236 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, 

1237 self.new_temp_name()) 

1238 gn_end = graph.Node(self.graph) 

1239 

1240 ### 1: LHS is true 

1241 self.start = current_start 

1242 self.attach_assumption(lhs_value) 

1243 self.attach_temp_declaration(n_expr, 

1244 sym_result, 

1245 smt.Boolean_Literal(True)) 

1246 self.start.add_edge_to(gn_end) 

1247 

1248 ### 2: LHS is not true 

1249 self.start = current_start 

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

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

1252 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1253 self.attach_temp_declaration(n_expr, 

1254 sym_result, 

1255 rhs_value) 

1256 self.start.add_edge_to(gn_end) 

1257 

1258 # Join paths 

1259 self.start = gn_end 

1260 

1261 return sym_result, smt.Boolean_Literal(True) 

1262 

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

1264 assert isinstance(n_component, Composite_Component) 

1265 assert isinstance(lhs, smt.Expression) 

1266 assert isinstance(rhs, smt.Expression) 

1267 

1268 value_lhs = smt.Record_Access(lhs, 

1269 n_component.name + ".value") 

1270 value_rhs = smt.Record_Access(rhs, 

1271 n_component.name + ".value") 

1272 valid_equal = self.tr_core_equality(n_component.n_typ, 

1273 value_lhs, 

1274 value_rhs) 

1275 

1276 if not n_component.optional: 

1277 return valid_equal 

1278 

1279 valid_lhs = smt.Record_Access(lhs, 

1280 n_component.name + ".valid") 

1281 valid_rhs = smt.Record_Access(rhs, 

1282 n_component.name + ".valid") 

1283 

1284 return smt.Conjunction( 

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

1286 smt.Implication(valid_lhs, valid_equal)) 

1287 

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

1289 assert isinstance(n_typ, Type) 

1290 assert isinstance(lhs, smt.Expression) 

1291 assert isinstance(rhs, smt.Expression) 

1292 

1293 if isinstance(n_typ, Tuple_Type): 

1294 parts = [] 

1295 for n_component in n_typ.all_components(): 

1296 parts.append( 

1297 self.tr_core_equality_tuple_component(n_component, 

1298 lhs, 

1299 rhs)) 

1300 

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

1302 return smt.Boolean_Literal(True) 

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

1304 return parts[0] 

1305 else: 

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

1307 for part in parts[2:]: 

1308 result = smt.Conjunction(result, part) 

1309 return result 

1310 

1311 else: 

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

1313 

1314 def tr_op_equality(self, n_expr): 

1315 assert isinstance(n_expr, Binary_Expression) 

1316 assert n_expr.operator in (Binary_Operator.COMP_EQ, 

1317 Binary_Operator.COMP_NEQ) 

1318 

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

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

1321 

1322 if lhs_value is None: 

1323 comp_typ = n_expr.n_rhs.typ 

1324 else: 

1325 comp_typ = n_expr.n_lhs.typ 

1326 

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

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

1329 result = self.tr_core_equality(comp_typ, 

1330 lhs_value, 

1331 rhs_value) 

1332 

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

1334 # This is null == null, so true 

1335 result = smt.Boolean_Literal(True) 

1336 

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

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

1339 result = smt.Boolean_Negation(rhs_valid) 

1340 

1341 elif rhs_value is None: 

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

1343 result = smt.Boolean_Negation(lhs_valid) 

1344 

1345 else: 

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

1347 result = smt.Conjunction( 

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

1349 smt.Implication(lhs_valid, 

1350 self.tr_core_equality(comp_typ, 

1351 lhs_value, 

1352 rhs_value))) 

1353 

1354 if n_expr.operator == Binary_Operator.COMP_NEQ: 

1355 result = smt.Boolean_Negation(result) 

1356 

1357 return self.create_return(n_expr, result) 

1358 

1359 def tr_quantified_expression(self, n_expr): 

1360 assert isinstance(n_expr, Quantified_Expression) 

1361 

1362 # Nested quantifiers are not supported yet 

1363 if self.functional: # pragma: no cover 

1364 self.flag_unsupported(n_expr, 

1365 "functional evaluation of quantifier") 

1366 

1367 # TRLC quantifier 

1368 # (forall x in arr_name => body) 

1369 # 

1370 # SMT quantifier 

1371 # (forall ((i Int)) 

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

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

1374 # 

1375 # There is an alternative which is: 

1376 # (forall ((element ElementSort)) 

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

1378 # (... element ...) 

1379 # 

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

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

1382 # value of some sequence member. 

1383 

1384 # Evaluate subject first and creat a null check 

1385 s_subject_value, s_subject_valid = \ 

1386 self.tr_name_reference(n_expr.n_source) 

1387 self.attach_validity_check(s_subject_valid, n_expr.n_source) 

1388 

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

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

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

1392 # it's valid). 

1393 current_start = self.start 

1394 self.attach_empty_assumption() 

1395 src_typ = n_expr.n_source.typ 

1396 assert isinstance(src_typ, Array_Type) 

1397 s_qe_index = smt.Constant(smt.BUILTIN_INTEGER, 

1398 self.new_temp_name()) 

1399 self.start.add_statement( 

1400 smt.Constant_Declaration( 

1401 symbol = s_qe_index, 

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

1403 (n_expr.to_string(), 

1404 n_expr.location.to_string())))) 

1405 self.start.add_statement( 

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

1407 s_qe_index, 

1408 smt.Integer_Literal(0)))) 

1409 self.start.add_statement( 

1410 smt.Assertion( 

1411 smt.Comparison("<", 

1412 s_qe_index, 

1413 smt.Sequence_Length(s_subject_value)))) 

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

1415 self.new_temp_name()) 

1416 self.start.add_statement( 

1417 smt.Constant_Declaration( 

1418 symbol = s_qe_sym, 

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

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

1421 (n_expr.to_string(), 

1422 n_expr.location.to_string())))) 

1423 self.qe_vars[n_expr.n_var] = s_qe_sym 

1424 

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

1426 self.attach_validity_check(b_valid, n_expr.n_expr) 

1427 

1428 self.start = current_start 

1429 del self.qe_vars[n_expr.n_var] 

1430 

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

1432 # raise exception. Asserting the actual value of the 

1433 # quantifier is more awkward. 

1434 

1435 s_q_idx = smt.Bound_Variable(smt.BUILTIN_INTEGER, 

1436 self.new_temp_name()) 

1437 s_q_sym = smt.Sequence_Index(s_subject_value, s_q_idx) 

1438 self.bound_vars[n_expr.n_var] = s_q_sym 

1439 

1440 temp, self.functional = self.functional, True 

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

1442 self.functional = temp 

1443 

1444 bounds_expr = smt.Conjunction( 

1445 smt.Comparison(">=", 

1446 s_q_idx, 

1447 smt.Integer_Literal(0)), 

1448 smt.Comparison("<", 

1449 s_q_idx, 

1450 smt.Sequence_Length(s_subject_value))) 

1451 if n_expr.universal: 

1452 value = smt.Quantifier( 

1453 "forall", 

1454 [s_q_idx], 

1455 smt.Implication(bounds_expr, b_value)) 

1456 else: 

1457 value = smt.Quantifier( 

1458 "exists", 

1459 [s_q_idx], 

1460 smt.Conjunction(bounds_expr, b_value)) 

1461 

1462 return value, smt.Boolean_Literal(True) 

1463 

1464 def tr_field_access_expression(self, n_expr): 

1465 assert isinstance(n_expr, Field_Access_Expression) 

1466 

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

1468 self.attach_validity_check(prefix_valid, n_expr.n_prefix) 

1469 

1470 field_value = smt.Record_Access(prefix_value, 

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

1472 if n_expr.n_field.optional: 

1473 field_valid = smt.Record_Access(prefix_value, 

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

1475 else: 

1476 field_valid = smt.Boolean_Literal(True) 

1477 

1478 return field_value, field_valid