Coverage for trlc/vcg.py: 97%

790 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-04-14 14:54 +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, Union_Type)): 

501 # lobster-trace: LRM.Union_Type_Equality 

502 if value < 0: 

503 instance_id = value * -2 - 1 

504 else: 

505 instance_id = value * 2 

506 if isinstance(n_typ, Record_Type): 506 ↛ 514line 506 didn't jump to line 514 because the condition on line 506 was always true

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

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

509 else: 

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

511 n_typ.name, 

512 instance_id) 

513 else: 

514 return "instance_%i" % instance_id 

515 

516 elif isinstance(n_typ, Tuple_Type): 

517 parts = [] 

518 for n_item in n_typ.iter_sequence(): 

519 if isinstance(n_item, Composite_Component): 

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

521 parts.pop() 

522 break 

523 parts.append( 

524 self.value_to_trlc(n_item.n_typ, 

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

526 

527 else: 

528 assert isinstance(n_item, Separator) 

529 sep_text = { 

530 "AT" : "@", 

531 "COLON" : ":", 

532 "SEMICOLON" : ";" 

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

534 parts.append(sep_text) 

535 

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

537 return "".join(parts) 

538 else: 

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

540 

541 elif isinstance(n_typ, Array_Type): 

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

543 item) 

544 for item in value) 

545 

546 else: # pragma: no cover 

547 self.flag_unsupported(n_typ, 

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

549 

550 def tr_component_value_name(self, n_component): 

551 return n_component.member_of.fully_qualified_name() + \ 

552 "." + n_component.name + ".value" 

553 

554 def tr_component_valid_name(self, n_component): 

555 return n_component.member_of.fully_qualified_name() + \ 

556 "." + n_component.name + ".valid" 

557 

558 def emit_tuple_constraints(self, n_tuple, s_sym): 

559 assert isinstance(n_tuple, Tuple_Type) 

560 assert isinstance(s_sym, smt.Constant) 

561 

562 old_functional, self.functional = self.functional, True 

563 self.tuple_base[n_tuple] = s_sym 

564 

565 constraints = [] 

566 

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

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

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

570 # record. 

571 

572 for n_check in n_tuple.iter_checks(): 

573 if n_check.severity == "warning": 

574 continue 

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

576 # truth here. 

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

578 constraints.append(c_value) 

579 

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

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

582 

583 components = n_tuple.all_components() 

584 for i, component in enumerate(components): 

585 if component.optional: 

586 condition = smt.Boolean_Negation( 

587 smt.Record_Access(s_sym, 

588 component.name + ".valid")) 

589 consequences = [ 

590 smt.Boolean_Negation( 

591 smt.Record_Access(s_sym, 

592 c.name + ".valid")) 

593 for c in components[i + 1:] 

594 ] 

595 if len(consequences) == 0: 

596 break 

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

598 consequence = consequences[0] 

599 else: 

600 consequence = smt.Conjunction(*consequences) 

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

602 

603 del self.tuple_base[n_tuple] 

604 self.functional = old_functional 

605 

606 for cons in constraints: 

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

608 

609 def tr_component_decl(self, n_component, gn_locals): 

610 assert isinstance(n_component, Composite_Component) 

611 assert isinstance(gn_locals, graph.Assumption) 

612 

613 if isinstance(self.n_ctyp, Record_Type): 

614 frozen = self.n_ctyp.is_frozen(n_component) 

615 else: 

616 frozen = False 

617 

618 id_value = self.tr_component_value_name(n_component) 

619 s_sort = self.tr_type(n_component.n_typ) 

620 s_sym = smt.Constant(s_sort, id_value) 

621 if frozen: 

622 old_functional, self.functional = self.functional, True 

623 s_val, _ = self.tr_expression( 

624 self.n_ctyp.get_freezing_expression(n_component)) 

625 self.functional = old_functional 

626 else: 

627 s_val = None 

628 s_decl = smt.Constant_Declaration( 

629 symbol = s_sym, 

630 value = s_val, 

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

632 n_component.name, 

633 n_component.location.to_string()), 

634 relevant = True) 

635 gn_locals.add_statement(s_decl) 

636 self.constants[id_value] = s_sym 

637 

638 if isinstance(n_component.n_typ, Tuple_Type): 

639 self.emit_tuple_constraints(n_component.n_typ, s_sym) 

640 

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

642 # length 

643 if isinstance(n_component.n_typ, Array_Type): 

644 if n_component.n_typ.lower_bound > 0: 

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

646 gn_locals.add_statement( 

647 smt.Assertion( 

648 smt.Comparison(">=", 

649 smt.Sequence_Length(s_sym), 

650 s_lower))) 

651 

652 if n_component.n_typ.upper_bound is not None: 

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

654 gn_locals.add_statement( 

655 smt.Assertion( 

656 smt.Comparison("<=", 

657 smt.Sequence_Length(s_sym), 

658 s_upper))) 

659 

660 id_valid = self.tr_component_valid_name(n_component) 

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

662 s_val = (None 

663 if n_component.optional and not frozen 

664 else smt.Boolean_Literal(True)) 

665 s_decl = smt.Constant_Declaration( 

666 symbol = s_sym, 

667 value = s_val, 

668 relevant = True) 

669 gn_locals.add_statement(s_decl) 

670 self.constants[id_valid] = s_sym 

671 

672 def tr_type(self, n_type): 

673 assert isinstance(n_type, Type) 

674 

675 if isinstance(n_type, Builtin_Boolean): 

676 return smt.BUILTIN_BOOLEAN 

677 

678 elif isinstance(n_type, Builtin_Integer): 

679 return smt.BUILTIN_INTEGER 

680 

681 elif isinstance(n_type, Builtin_Decimal): 

682 return smt.BUILTIN_REAL 

683 

684 elif isinstance(n_type, Builtin_String): 

685 return smt.BUILTIN_STRING 

686 

687 elif isinstance(n_type, Enumeration_Type): 

688 if n_type not in self.enumerations: 

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

690 "." + n_type.name) 

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

692 s_sort.add_literal(n_lit.name) 

693 self.enumerations[n_type] = s_sort 

694 self.start.add_statement( 

695 smt.Enumeration_Declaration( 

696 s_sort, 

697 "enumeration %s from %s" % ( 

698 n_type.name, 

699 n_type.location.to_string()))) 

700 return self.enumerations[n_type] 

701 

702 elif isinstance(n_type, Tuple_Type): 

703 if n_type not in self.tuples: 

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

705 "." + n_type.name) 

706 for n_component in n_type.all_components(): 

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

708 self.tr_type(n_component.n_typ)) 

709 if n_component.optional: 

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

711 smt.BUILTIN_BOOLEAN) 

712 self.tuples[n_type] = s_sort 

713 self.start.add_statement( 

714 smt.Record_Declaration( 

715 s_sort, 

716 "tuple %s from %s" % ( 

717 n_type.name, 

718 n_type.location.to_string()))) 

719 

720 return self.tuples[n_type] 

721 

722 elif isinstance(n_type, Array_Type): 

723 if n_type not in self.arrays: 

724 s_element_sort = self.tr_type(n_type.element_type) 

725 s_sequence = smt.Sequence_Sort(s_element_sort) 

726 self.arrays[n_type] = s_sequence 

727 

728 return self.arrays[n_type] 

729 

730 elif isinstance(n_type, (Record_Type, Union_Type)): 

731 # lobster-trace: LRM.union_type 

732 # Record and union references are modelled as a free 

733 # integer. If we access their field then we use an 

734 # uninterpreted function. Some of these have special 

735 # meaning: 

736 # 0 - the null reference 

737 # 1 - the self reference 

738 # anything else - uninterpreted 

739 return smt.BUILTIN_INTEGER 

740 

741 else: # pragma: no cover 

742 self.flag_unsupported(n_type) 

743 

744 def tr_check(self, n_check): 

745 assert isinstance(n_check, Check) 

746 

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

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

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

750 # failed. 

751 if n_check.n_type is not self.n_ctyp: 

752 old_emit, self.emit_checks = self.emit_checks, False 

753 

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

755 self.attach_validity_check(valid, n_check.n_expr) 

756 self.attach_feasability_check(value, n_check.n_expr) 

757 self.attach_assumption(value) 

758 

759 if n_check.n_type is not self.n_ctyp: 

760 self.emit_checks = old_emit 

761 

762 def tr_expression(self, n_expr): 

763 value = None 

764 

765 if isinstance(n_expr, Name_Reference): 

766 return self.tr_name_reference(n_expr) 

767 

768 elif isinstance(n_expr, Unary_Expression): 

769 return self.tr_unary_expression(n_expr) 

770 

771 elif isinstance(n_expr, Binary_Expression): 

772 return self.tr_binary_expression(n_expr) 

773 

774 elif isinstance(n_expr, Range_Test): 

775 return self.tr_range_test(n_expr) 

776 

777 elif isinstance(n_expr, OneOf_Expression): 

778 return self.tr_oneof_test(n_expr) 

779 

780 elif isinstance(n_expr, Conditional_Expression): 

781 if self.functional: 

782 return self.tr_conditional_expression_functional(n_expr) 

783 else: 

784 return self.tr_conditional_expression(n_expr) 

785 

786 elif isinstance(n_expr, Null_Literal): 

787 return None, smt.Boolean_Literal(False) 

788 

789 elif isinstance(n_expr, Boolean_Literal): 

790 value = smt.Boolean_Literal(n_expr.value) 

791 

792 elif isinstance(n_expr, Integer_Literal): 

793 value = smt.Integer_Literal(n_expr.value) 

794 

795 elif isinstance(n_expr, Decimal_Literal): 

796 value = smt.Real_Literal(n_expr.value) 

797 

798 elif isinstance(n_expr, Enumeration_Literal): 

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

800 n_expr.value.name) 

801 

802 elif isinstance(n_expr, String_Literal): 

803 value = smt.String_Literal(n_expr.value) 

804 

805 elif isinstance(n_expr, Quantified_Expression): 

806 return self.tr_quantified_expression(n_expr) 

807 

808 elif isinstance(n_expr, Field_Access_Expression): 

809 return self.tr_field_access_expression(n_expr) 

810 

811 else: # pragma: no cover 

812 self.flag_unsupported(n_expr) 

813 

814 return value, smt.Boolean_Literal(True) 

815 

816 def tr_name_reference(self, n_ref): 

817 assert isinstance(n_ref, Name_Reference) 

818 

819 if isinstance(n_ref.entity, Composite_Component): 

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

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

822 if n_ref.entity.optional: 

823 s_valid = smt.Record_Access(sym, 

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

825 else: 

826 s_valid = smt.Boolean_Literal(True) 

827 s_value = smt.Record_Access(sym, 

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

829 return s_value, s_valid 

830 

831 else: 

832 id_value = self.tr_component_value_name(n_ref.entity) 

833 id_valid = self.tr_component_valid_name(n_ref.entity) 

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

835 

836 else: 

837 assert isinstance(n_ref.entity, Quantified_Variable) 

838 if n_ref.entity in self.qe_vars: 

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

840 else: 

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

842 

843 def tr_unary_expression(self, n_expr): 

844 assert isinstance(n_expr, Unary_Expression) 

845 

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

847 if not self.functional: 

848 self.attach_validity_check(operand_valid, n_expr.n_operand) 

849 

850 sym_value = None 

851 

852 if n_expr.operator == Unary_Operator.MINUS: 

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

854 sym_value = smt.Unary_Int_Arithmetic_Op("-", 

855 operand_value) 

856 else: 

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

858 sym_value = smt.Unary_Real_Arithmetic_Op("-", 

859 operand_value) 

860 

861 elif n_expr.operator == Unary_Operator.PLUS: 

862 sym_value = operand_value 

863 

864 elif n_expr.operator == Unary_Operator.LOGICAL_NOT: 

865 sym_value = smt.Boolean_Negation(operand_value) 

866 

867 elif n_expr.operator == Unary_Operator.ABSOLUTE_VALUE: 

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

869 sym_value = smt.Unary_Int_Arithmetic_Op("abs", 

870 operand_value) 

871 

872 else: 

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

874 sym_value = smt.Unary_Real_Arithmetic_Op("abs", 

875 operand_value) 

876 

877 elif n_expr.operator == Unary_Operator.STRING_LENGTH: 

878 sym_value = smt.String_Length(operand_value) 

879 

880 elif n_expr.operator == Unary_Operator.ARRAY_LENGTH: 

881 sym_value = smt.Sequence_Length(operand_value) 

882 

883 elif n_expr.operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

884 sym_value = smt.Conversion_To_Real(operand_value) 

885 

886 elif n_expr.operator == Unary_Operator.CONVERSION_TO_INT: 

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

888 

889 else: 

890 self.mh.ice_loc(n_expr, 

891 "unexpected unary operator %s" % 

892 n_expr.operator.name) 

893 

894 return self.create_return(n_expr, sym_value) 

895 

896 def tr_binary_expression(self, n_expr): 

897 assert isinstance(n_expr, Binary_Expression) 

898 

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

900 # deal with them first and then exit. 

901 if n_expr.operator in (Binary_Operator.COMP_EQ, 

902 Binary_Operator.COMP_NEQ): 

903 return self.tr_op_equality(n_expr) 

904 

905 elif n_expr.operator == Binary_Operator.LOGICAL_IMPLIES: 

906 return self.tr_op_implication(n_expr) 

907 

908 elif n_expr.operator == Binary_Operator.LOGICAL_AND: 

909 return self.tr_op_and(n_expr) 

910 

911 elif n_expr.operator == Binary_Operator.LOGICAL_OR: 

912 return self.tr_op_or(n_expr) 

913 

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

915 # obtain the values of both sides now. 

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

917 if not self.functional: 

918 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

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

920 if not self.functional: 

921 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

922 sym_value = None 

923 

924 if n_expr.operator == Binary_Operator.LOGICAL_XOR: 

925 sym_value = smt.Exclusive_Disjunction(lhs_value, rhs_value) 

926 

927 elif n_expr.operator in (Binary_Operator.PLUS, 

928 Binary_Operator.MINUS, 

929 Binary_Operator.TIMES, 

930 Binary_Operator.DIVIDE, 

931 Binary_Operator.REMAINDER): 

932 

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

934 assert n_expr.operator == Binary_Operator.PLUS 

935 sym_value = smt.String_Concatenation(lhs_value, rhs_value) 

936 

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

938 if n_expr.operator in (Binary_Operator.DIVIDE, 

939 Binary_Operator.REMAINDER): 

940 self.attach_int_division_check(rhs_value, n_expr) 

941 

942 smt_op = { 

943 Binary_Operator.PLUS : "+", 

944 Binary_Operator.MINUS : "-", 

945 Binary_Operator.TIMES : "*", 

946 Binary_Operator.DIVIDE : "floor_div", 

947 Binary_Operator.REMAINDER : "ada_remainder", 

948 }[n_expr.operator] 

949 

950 sym_value = smt.Binary_Int_Arithmetic_Op(smt_op, 

951 lhs_value, 

952 rhs_value) 

953 

954 else: 

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

956 if n_expr.operator == Binary_Operator.DIVIDE: 

957 self.attach_real_division_check(rhs_value, n_expr) 

958 

959 smt_op = { 

960 Binary_Operator.PLUS : "+", 

961 Binary_Operator.MINUS : "-", 

962 Binary_Operator.TIMES : "*", 

963 Binary_Operator.DIVIDE : "/", 

964 }[n_expr.operator] 

965 

966 sym_value = smt.Binary_Real_Arithmetic_Op(smt_op, 

967 lhs_value, 

968 rhs_value) 

969 

970 elif n_expr.operator in (Binary_Operator.COMP_LT, 

971 Binary_Operator.COMP_LEQ, 

972 Binary_Operator.COMP_GT, 

973 Binary_Operator.COMP_GEQ): 

974 smt_op = { 

975 Binary_Operator.COMP_LT : "<", 

976 Binary_Operator.COMP_LEQ : "<=", 

977 Binary_Operator.COMP_GT : ">", 

978 Binary_Operator.COMP_GEQ : ">=", 

979 }[n_expr.operator] 

980 

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

982 

983 elif n_expr.operator in (Binary_Operator.STRING_CONTAINS, 

984 Binary_Operator.STRING_STARTSWITH, 

985 Binary_Operator.STRING_ENDSWITH): 

986 

987 smt_op = { 

988 Binary_Operator.STRING_CONTAINS : "contains", 

989 Binary_Operator.STRING_STARTSWITH : "prefixof", 

990 Binary_Operator.STRING_ENDSWITH : "suffixof" 

991 } 

992 

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

994 # other way around than in TRLC. 

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

996 rhs_value, 

997 lhs_value) 

998 

999 elif n_expr.operator == Binary_Operator.STRING_REGEX: 

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

1001 assert isinstance(rhs_evaluation, str) 

1002 

1003 sym_value = smt.Function_Application( 

1004 self.get_uf_matches(), 

1005 lhs_value, 

1006 smt.String_Literal(rhs_evaluation)) 

1007 

1008 elif n_expr.operator == Binary_Operator.INDEX: 

1009 self.attach_index_check(lhs_value, rhs_value, n_expr) 

1010 sym_value = smt.Sequence_Index(lhs_value, rhs_value) 

1011 

1012 elif n_expr.operator == Binary_Operator.ARRAY_CONTAINS: 

1013 sym_value = smt.Sequence_Contains(rhs_value, lhs_value) 

1014 

1015 elif n_expr.operator == Binary_Operator.POWER: 

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

1017 # integer 

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

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

1020 

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

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

1023 sym_value = smt.Integer_Literal(1) 

1024 else: 

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

1026 sym_value = smt.Real_Literal(1) 

1027 

1028 else: 

1029 sym_value = lhs_value 

1030 for _ in range(1, static_value): 

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

1032 sym_value = smt.Binary_Int_Arithmetic_Op("*", 

1033 sym_value, 

1034 lhs_value) 

1035 else: 

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

1037 sym_value = smt.Binary_Real_Arithmetic_Op("*", 

1038 sym_value, 

1039 lhs_value) 

1040 

1041 else: # pragma: no cover 

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

1043 

1044 return self.create_return(n_expr, sym_value) 

1045 

1046 def tr_range_test(self, n_expr): 

1047 assert isinstance(n_expr, Range_Test) 

1048 

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

1050 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

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

1052 self.attach_validity_check(lower_valid, n_expr.n_lower) 

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

1054 self.attach_validity_check(upper_valid, n_expr.n_upper) 

1055 

1056 sym_value = smt.Conjunction( 

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

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

1059 

1060 return self.create_return(n_expr, sym_value) 

1061 

1062 def tr_oneof_test(self, n_expr): 

1063 assert isinstance(n_expr, OneOf_Expression) 

1064 

1065 choices = [] 

1066 for n_choice in n_expr.choices: 

1067 c_value, c_valid = self.tr_expression(n_choice) 

1068 self.attach_validity_check(c_valid, n_choice) 

1069 choices.append(c_value) 

1070 

1071 negated_choices = [smt.Boolean_Negation(c) 

1072 for c in choices] 

1073 

1074 # pylint: disable=consider-using-enumerate 

1075 

1076 if len(choices) == 1: 

1077 result = choices[0] 

1078 elif len(choices) == 2: 

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

1080 else: 

1081 assert len(choices) >= 3 

1082 values = [] 

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

1084 sequence = [] 

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

1086 if other_id == choice_id: 

1087 sequence.append(choices[other_id]) 

1088 else: 

1089 sequence.append(negated_choices[other_id]) 

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

1091 result = smt.Disjunction(*values) 

1092 

1093 return self.create_return(n_expr, result) 

1094 

1095 def tr_conditional_expression_functional(self, n_expr): 

1096 assert isinstance(n_expr, Conditional_Expression) 

1097 

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

1099 for n_action in reversed(n_expr.actions): 

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

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

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

1103 

1104 return self.create_return(n_expr, s_result) 

1105 

1106 def tr_conditional_expression(self, n_expr): 

1107 assert isinstance(n_expr, Conditional_Expression) 

1108 assert not self.functional 

1109 

1110 gn_end = graph.Node(self.graph) 

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

1112 self.new_temp_name()) 

1113 

1114 for n_action in n_expr.actions: 

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

1116 self.attach_validity_check(test_valid, n_action.n_cond) 

1117 current_start = self.start 

1118 

1119 # Create path where action is true 

1120 self.attach_assumption(test_value) 

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

1122 self.attach_validity_check(res_valid, n_action.n_expr) 

1123 self.attach_temp_declaration(n_action, 

1124 sym_result, 

1125 res_value) 

1126 self.start.add_edge_to(gn_end) 

1127 

1128 # Reset to test and proceed with the other actions 

1129 self.start = current_start 

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

1131 

1132 # Finally execute the else part 

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

1134 self.attach_validity_check(res_valid, n_expr.else_expr) 

1135 self.attach_temp_declaration(n_expr, 

1136 sym_result, 

1137 res_value) 

1138 self.start.add_edge_to(gn_end) 

1139 

1140 # And join 

1141 self.start = gn_end 

1142 return sym_result, smt.Boolean_Literal(True) 

1143 

1144 def tr_op_implication(self, n_expr): 

1145 assert isinstance(n_expr, Binary_Expression) 

1146 assert n_expr.operator == Binary_Operator.LOGICAL_IMPLIES 

1147 

1148 if self.functional: 

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

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

1151 return self.create_return(n_expr, 

1152 smt.Implication(lhs_value, rhs_value)) 

1153 

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

1155 # Emit VC for validity 

1156 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1157 

1158 # Split into two paths. 

1159 current_start = self.start 

1160 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, 

1161 self.new_temp_name()) 

1162 gn_end = graph.Node(self.graph) 

1163 

1164 ### 1: Implication is not valid 

1165 self.start = current_start 

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

1167 self.attach_temp_declaration(n_expr, 

1168 sym_result, 

1169 smt.Boolean_Literal(True)) 

1170 self.start.add_edge_to(gn_end) 

1171 

1172 ### 2: Implication is valid. 

1173 self.start = current_start 

1174 self.attach_assumption(lhs_value) 

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

1176 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1177 self.attach_temp_declaration(n_expr, 

1178 sym_result, 

1179 rhs_value) 

1180 self.start.add_edge_to(gn_end) 

1181 

1182 # Join paths 

1183 self.start = gn_end 

1184 

1185 return sym_result, smt.Boolean_Literal(True) 

1186 

1187 def tr_op_and(self, n_expr): 

1188 assert isinstance(n_expr, Binary_Expression) 

1189 assert n_expr.operator == Binary_Operator.LOGICAL_AND 

1190 

1191 if self.functional: 

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

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

1194 return self.create_return(n_expr, 

1195 smt.Conjunction(lhs_value, rhs_value)) 

1196 

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

1198 # Emit VC for validity 

1199 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1200 

1201 # Split into two paths. 

1202 current_start = self.start 

1203 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, 

1204 self.new_temp_name()) 

1205 gn_end = graph.Node(self.graph) 

1206 

1207 ### 1: LHS is not true 

1208 self.start = current_start 

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

1210 self.attach_temp_declaration(n_expr, 

1211 sym_result, 

1212 smt.Boolean_Literal(False)) 

1213 self.start.add_edge_to(gn_end) 

1214 

1215 ### 2: LHS is true 

1216 self.start = current_start 

1217 self.attach_assumption(lhs_value) 

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

1219 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1220 self.attach_temp_declaration(n_expr, 

1221 sym_result, 

1222 rhs_value) 

1223 self.start.add_edge_to(gn_end) 

1224 

1225 # Join paths 

1226 self.start = gn_end 

1227 

1228 return sym_result, smt.Boolean_Literal(True) 

1229 

1230 def tr_op_or(self, n_expr): 

1231 assert isinstance(n_expr, Binary_Expression) 

1232 assert n_expr.operator == Binary_Operator.LOGICAL_OR 

1233 

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

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

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

1237 return self.create_return(n_expr, 

1238 smt.Disjunction(lhs_value, rhs_value)) 

1239 

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

1241 # Emit VC for validity 

1242 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1243 

1244 # Split into two paths. 

1245 current_start = self.start 

1246 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, 

1247 self.new_temp_name()) 

1248 gn_end = graph.Node(self.graph) 

1249 

1250 ### 1: LHS is true 

1251 self.start = current_start 

1252 self.attach_assumption(lhs_value) 

1253 self.attach_temp_declaration(n_expr, 

1254 sym_result, 

1255 smt.Boolean_Literal(True)) 

1256 self.start.add_edge_to(gn_end) 

1257 

1258 ### 2: LHS is not true 

1259 self.start = current_start 

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

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

1262 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1263 self.attach_temp_declaration(n_expr, 

1264 sym_result, 

1265 rhs_value) 

1266 self.start.add_edge_to(gn_end) 

1267 

1268 # Join paths 

1269 self.start = gn_end 

1270 

1271 return sym_result, smt.Boolean_Literal(True) 

1272 

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

1274 assert isinstance(n_component, Composite_Component) 

1275 assert isinstance(lhs, smt.Expression) 

1276 assert isinstance(rhs, smt.Expression) 

1277 

1278 value_lhs = smt.Record_Access(lhs, 

1279 n_component.name + ".value") 

1280 value_rhs = smt.Record_Access(rhs, 

1281 n_component.name + ".value") 

1282 valid_equal = self.tr_core_equality(n_component.n_typ, 

1283 value_lhs, 

1284 value_rhs) 

1285 

1286 if not n_component.optional: 

1287 return valid_equal 

1288 

1289 valid_lhs = smt.Record_Access(lhs, 

1290 n_component.name + ".valid") 

1291 valid_rhs = smt.Record_Access(rhs, 

1292 n_component.name + ".valid") 

1293 

1294 return smt.Conjunction( 

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

1296 smt.Implication(valid_lhs, valid_equal)) 

1297 

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

1299 assert isinstance(n_typ, Type) 

1300 assert isinstance(lhs, smt.Expression) 

1301 assert isinstance(rhs, smt.Expression) 

1302 

1303 if isinstance(n_typ, Tuple_Type): 

1304 parts = [] 

1305 for n_component in n_typ.all_components(): 

1306 parts.append( 

1307 self.tr_core_equality_tuple_component(n_component, 

1308 lhs, 

1309 rhs)) 

1310 

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

1312 return smt.Boolean_Literal(True) 

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

1314 return parts[0] 

1315 else: 

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

1317 for part in parts[2:]: 

1318 result = smt.Conjunction(result, part) 

1319 return result 

1320 

1321 else: 

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

1323 

1324 def tr_op_equality(self, n_expr): 

1325 assert isinstance(n_expr, Binary_Expression) 

1326 assert n_expr.operator in (Binary_Operator.COMP_EQ, 

1327 Binary_Operator.COMP_NEQ) 

1328 

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

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

1331 

1332 if lhs_value is None: 

1333 comp_typ = n_expr.n_rhs.typ 

1334 else: 

1335 comp_typ = n_expr.n_lhs.typ 

1336 

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

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

1339 result = self.tr_core_equality(comp_typ, 

1340 lhs_value, 

1341 rhs_value) 

1342 

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

1344 # This is null == null, so true 

1345 result = smt.Boolean_Literal(True) 

1346 

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

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

1349 result = smt.Boolean_Negation(rhs_valid) 

1350 

1351 elif rhs_value is None: 

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

1353 result = smt.Boolean_Negation(lhs_valid) 

1354 

1355 else: 

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

1357 result = smt.Conjunction( 

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

1359 smt.Implication(lhs_valid, 

1360 self.tr_core_equality(comp_typ, 

1361 lhs_value, 

1362 rhs_value))) 

1363 

1364 if n_expr.operator == Binary_Operator.COMP_NEQ: 

1365 result = smt.Boolean_Negation(result) 

1366 

1367 return self.create_return(n_expr, result) 

1368 

1369 def tr_quantified_expression(self, n_expr): 

1370 assert isinstance(n_expr, Quantified_Expression) 

1371 

1372 # Nested quantifiers are not supported yet 

1373 if self.functional: # pragma: no cover 

1374 self.flag_unsupported(n_expr, 

1375 "functional evaluation of quantifier") 

1376 

1377 # TRLC quantifier 

1378 # (forall x in arr_name => body) 

1379 # 

1380 # SMT quantifier 

1381 # (forall ((i Int)) 

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

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

1384 # 

1385 # There is an alternative which is: 

1386 # (forall ((element ElementSort)) 

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

1388 # (... element ...) 

1389 # 

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

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

1392 # value of some sequence member. 

1393 

1394 # Evaluate subject first and creat a null check 

1395 s_subject_value, s_subject_valid = \ 

1396 self.tr_name_reference(n_expr.n_source) 

1397 self.attach_validity_check(s_subject_valid, n_expr.n_source) 

1398 

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

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

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

1402 # it's valid). 

1403 current_start = self.start 

1404 self.attach_empty_assumption() 

1405 src_typ = n_expr.n_source.typ 

1406 assert isinstance(src_typ, Array_Type) 

1407 s_qe_index = smt.Constant(smt.BUILTIN_INTEGER, 

1408 self.new_temp_name()) 

1409 self.start.add_statement( 

1410 smt.Constant_Declaration( 

1411 symbol = s_qe_index, 

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

1413 (n_expr.to_string(), 

1414 n_expr.location.to_string())))) 

1415 self.start.add_statement( 

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

1417 s_qe_index, 

1418 smt.Integer_Literal(0)))) 

1419 self.start.add_statement( 

1420 smt.Assertion( 

1421 smt.Comparison("<", 

1422 s_qe_index, 

1423 smt.Sequence_Length(s_subject_value)))) 

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

1425 self.new_temp_name()) 

1426 self.start.add_statement( 

1427 smt.Constant_Declaration( 

1428 symbol = s_qe_sym, 

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

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

1431 (n_expr.to_string(), 

1432 n_expr.location.to_string())))) 

1433 self.qe_vars[n_expr.n_var] = s_qe_sym 

1434 

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

1436 self.attach_validity_check(b_valid, n_expr.n_expr) 

1437 

1438 self.start = current_start 

1439 del self.qe_vars[n_expr.n_var] 

1440 

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

1442 # raise exception. Asserting the actual value of the 

1443 # quantifier is more awkward. 

1444 

1445 s_q_idx = smt.Bound_Variable(smt.BUILTIN_INTEGER, 

1446 self.new_temp_name()) 

1447 s_q_sym = smt.Sequence_Index(s_subject_value, s_q_idx) 

1448 self.bound_vars[n_expr.n_var] = s_q_sym 

1449 

1450 temp, self.functional = self.functional, True 

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

1452 self.functional = temp 

1453 

1454 bounds_expr = smt.Conjunction( 

1455 smt.Comparison(">=", 

1456 s_q_idx, 

1457 smt.Integer_Literal(0)), 

1458 smt.Comparison("<", 

1459 s_q_idx, 

1460 smt.Sequence_Length(s_subject_value))) 

1461 if n_expr.universal: 

1462 value = smt.Quantifier( 

1463 "forall", 

1464 [s_q_idx], 

1465 smt.Implication(bounds_expr, b_value)) 

1466 else: 

1467 value = smt.Quantifier( 

1468 "exists", 

1469 [s_q_idx], 

1470 smt.Conjunction(bounds_expr, b_value)) 

1471 

1472 return value, smt.Boolean_Literal(True) 

1473 

1474 def _ensure_record_deref(self, type_key, sort_name, uf_name, 

1475 components): 

1476 """Lazily create an SMT record sort and uninterpreted function 

1477 for dereferencing integer-encoded references. 

1478 

1479 :param type_key: cache key; always the canonical sort_name string 

1480 :param sort_name: name for the SMT Record sort 

1481 :param uf_name: name for the UF mapping integer to sort 

1482 :param components: iterable of (field_name, smt_sort, needs_valid) 

1483 :returns: (record_sort, to_record_uf) 

1484 """ 

1485 if type_key in self.records: 

1486 return self.records[type_key], self.uf_records[type_key] 

1487 

1488 record_sort = smt.Record(sort_name) 

1489 for field_name, field_sort, needs_valid in components: 

1490 record_sort.add_component(field_name + ".value", field_sort) 

1491 if needs_valid: 

1492 record_sort.add_component(field_name + ".valid", 

1493 smt.BUILTIN_BOOLEAN) 

1494 self.records[type_key] = record_sort 

1495 self.preamble.add_statement( 

1496 smt.Record_Declaration( 

1497 record_sort, 

1498 sort_name)) 

1499 

1500 to_record_uf = smt.Function( 

1501 uf_name, record_sort, 

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

1503 self.preamble.add_statement( 

1504 smt.Function_Declaration(to_record_uf)) 

1505 self.uf_records[type_key] = to_record_uf 

1506 

1507 return record_sort, to_record_uf 

1508 

1509 def tr_field_access_expression(self, n_expr): 

1510 assert isinstance(n_expr, Field_Access_Expression) 

1511 

1512 if self.functional: # pragma: no cover 

1513 self.flag_unsupported(n_expr, 

1514 "functional evaluation of field access") 

1515 

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

1517 prefix_typ = n_expr.n_prefix.typ 

1518 self.attach_validity_check(prefix_valid, n_expr.n_prefix) 

1519 

1520 if isinstance(prefix_typ, Tuple_Type): 

1521 field_value = smt.Record_Access(prefix_value, 

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

1523 if n_expr.n_field.optional: 

1524 field_valid = smt.Record_Access(prefix_value, 

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

1526 else: 

1527 field_valid = smt.Boolean_Literal(True) 

1528 

1529 elif isinstance(prefix_typ, (Record_Type, Union_Type)): 

1530 # lobster-trace: LRM.Union_Type_Field_Access 

1531 # lobster-trace: LRM.Union_Type_Partial_Field_Access 

1532 # lobster-trace: LRM.Union_Type_Partial_Field_Null 

1533 # Both Record_Type and Union_Type are represented as 

1534 # integers. We create a record sort with accessible 

1535 # fields and a UF to dereference the integer. 

1536 if isinstance(prefix_typ, Record_Type): 

1537 components = [ 

1538 (c.name, self.tr_type(c.n_typ), c.optional) 

1539 for c in prefix_typ.all_components() 

1540 ] 

1541 sort_name = "%s.%s" % (prefix_typ.n_package.name, 

1542 prefix_typ.name) 

1543 uf_name = "access.%s.%s" % (prefix_typ.n_package.name, 

1544 prefix_typ.name) 

1545 else: 

1546 field_map = prefix_typ.get_field_map() 

1547 union_id = "_".join(t.fully_qualified_name() 

1548 for t in prefix_typ.types) 

1549 components = [ 

1550 (name, 

1551 self.tr_type(info["n_typ"]), 

1552 info["count"] != info["total"] or 

1553 info["optional_in_any"]) 

1554 for name, info in field_map.items() 

1555 if info["n_typ"] is not None 

1556 ] 

1557 sort_name = "union." + union_id 

1558 uf_name = "access.union." + union_id 

1559 

1560 _, to_record_uf = self._ensure_record_deref( 

1561 sort_name, sort_name, uf_name, components) 

1562 dereference = smt.Function_Application(to_record_uf, 

1563 prefix_value) 

1564 

1565 # Perform the field access on the dereferenced record 

1566 field_value = smt.Record_Access(dereference, 

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

1568 if isinstance(prefix_typ, Union_Type): 

1569 info = prefix_typ.get_field_map()[n_expr.n_field.name] 

1570 has_valid = (info["count"] != info["total"] or 

1571 info["optional_in_any"]) 

1572 else: 

1573 has_valid = n_expr.n_field.optional 

1574 

1575 if has_valid: 

1576 field_valid = smt.Record_Access( 

1577 dereference, 

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

1579 else: 

1580 field_valid = smt.Boolean_Literal(True) 

1581 

1582 else: 

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

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

1585 n_expr.n_prefix.typ.__class__.__name__) 

1586 

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

1588 return field_value, field_valid