Coverage for trlc/vcg.py: 97%

787 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-07-07 15:09 +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 VCG_AVAILABLE = True 

33except ImportError: # pragma: no cover 

34 VCG_AVAILABLE = False 

35 

36try: 

37 from pyvcg.driver.cvc5_api import CVC5_Solver 

38 CVC5_API_AVAILABLE = True 

39except ImportError: # pragma: no cover 

40 CVC5_API_AVAILABLE = False 

41 

42CVC5_OPTIONS = { 

43 "tlimit-per" : 2500, 

44 "seed" : 42, 

45 "sat-random-seed" : 42, 

46} 

47 

48 

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

50 # lobster-exclude: Not safety relevant 

51 def __init__(self, node, text): 

52 assert isinstance(node, Node) 

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

54 super().__init__() 

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

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

57 self.location = node.location 

58 

59 

60class Feedback: 

61 # lobster-exclude: Not safety relevant 

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

63 assert isinstance(node, Expression) 

64 assert isinstance(message, str) 

65 assert isinstance(kind, str) 

66 assert isinstance(expect_unsat, bool) 

67 self.node = node 

68 self.message = message 

69 self.kind = "vcg-" + kind 

70 self.expect_unsat = expect_unsat 

71 

72 

73class VCG: 

74 # lobster-exclude: Not safety relevant 

75 def __init__(self, mh, n_ctyp, debug): 

76 assert VCG_AVAILABLE 

77 assert isinstance(mh, Message_Handler) 

78 assert isinstance(n_ctyp, Composite_Type) 

79 assert isinstance(debug, bool) 

80 

81 self.mh = mh 

82 self.n_ctyp = n_ctyp 

83 self.debug = debug 

84 

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

86 n_ctyp.name) 

87 

88 self.tmp_id = 0 

89 

90 self.vcg = vcg.VCG() 

91 self.graph = self.vcg.graph 

92 self.start = self.vcg.start 

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

94 self.preamble = None 

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

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

97 

98 self.constants = {} 

99 self.enumerations = {} 

100 self.tuples = {} 

101 self.records = {} 

102 self.arrays = {} 

103 self.bound_vars = {} 

104 self.qe_vars = {} 

105 self.tuple_base = {} 

106 self.uf_records = {} 

107 

108 self.uf_matches = None 

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

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

111 # string theory being used. 

112 

113 self.functional = False 

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

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

116 # expresions and return them. 

117 

118 self.emit_checks = True 

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

120 

121 @staticmethod 

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

123 assert isinstance(node, Node) 

124 raise Unsupported(node, text) 

125 

126 def new_temp_name(self): 

127 self.tmp_id += 1 

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

129 

130 def get_uf_matches(self): 

131 if self.uf_matches is None: 

132 self.uf_matches = \ 

133 smt.Function("trlc.matches", 

134 smt.BUILTIN_BOOLEAN, 

135 smt.Bound_Variable(smt.BUILTIN_STRING, 

136 "subject"), 

137 smt.Bound_Variable(smt.BUILTIN_STRING, 

138 "regex")) 

139 

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

141 # will deal with regex properly). 

142 self.preamble.add_statement( 

143 smt.Function_Declaration(self.uf_matches)) 

144 

145 return self.uf_matches 

146 

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

148 assert isinstance(node, Expression) 

149 assert isinstance(s_value, smt.Expression) 

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

151 

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

153 s_valid = smt.Boolean_Literal(True) 

154 

155 if self.functional: 

156 return s_value, s_valid 

157 

158 else: 

159 sym_result = smt.Constant(s_value.sort, 

160 self.new_temp_name()) 

161 self.attach_temp_declaration(node, sym_result, s_value) 

162 

163 return sym_result, s_valid 

164 

165 def attach_validity_check(self, bool_expr, origin): 

166 assert isinstance(bool_expr, smt.Expression) 

167 assert bool_expr.sort is smt.BUILTIN_BOOLEAN 

168 assert isinstance(origin, Expression) 

169 assert not self.functional 

170 

171 if not self.emit_checks: 

172 return 

173 

174 # Attach new graph node advance start 

175 if not bool_expr.is_static_true(): 

176 gn_check = graph.Check(self.graph) 

177 gn_check.add_goal(bool_expr, 

178 Feedback(origin, 

179 "expression could be null", 

180 "evaluation-of-null"), 

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

182 self.start.add_edge_to(gn_check) 

183 self.start = gn_check 

184 

185 def attach_int_division_check(self, int_expr, origin): 

186 assert isinstance(int_expr, smt.Expression) 

187 assert int_expr.sort is smt.BUILTIN_INTEGER 

188 assert isinstance(origin, Expression) 

189 assert not self.functional 

190 

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

192 return 

193 

194 # Attach new graph node advance start 

195 gn_check = graph.Check(self.graph) 

196 gn_check.add_goal( 

197 smt.Boolean_Negation( 

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

199 Feedback(origin, 

200 "divisor could be 0", 

201 "div-by-zero"), 

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

203 self.start.add_edge_to(gn_check) 

204 self.start = gn_check 

205 

206 def attach_real_division_check(self, real_expr, origin): 

207 assert isinstance(real_expr, smt.Expression) 

208 assert real_expr.sort is smt.BUILTIN_REAL 

209 assert isinstance(origin, Expression) 

210 assert not self.functional 

211 

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

213 return 

214 

215 # Attach new graph node advance start 

216 gn_check = graph.Check(self.graph) 

217 gn_check.add_goal( 

218 smt.Boolean_Negation( 

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

220 Feedback(origin, 

221 "divisor could be 0.0", 

222 "div-by-zero"), 

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

224 self.start.add_edge_to(gn_check) 

225 self.start = gn_check 

226 

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

228 assert isinstance(seq_expr, smt.Expression) 

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

230 assert isinstance(index_expr, smt.Expression) 

231 assert index_expr.sort is smt.BUILTIN_INTEGER 

232 assert isinstance(origin, Binary_Expression) 

233 assert origin.operator == Binary_Operator.INDEX 

234 assert not self.functional 

235 

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

237 return 

238 

239 # Attach new graph node advance start 

240 gn_check = graph.Check(self.graph) 

241 gn_check.add_goal( 

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

243 Feedback(origin, 

244 "array index could be less than 0", 

245 "array-index"), 

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

247 gn_check.add_goal( 

248 smt.Comparison("<", 

249 index_expr, 

250 smt.Sequence_Length(seq_expr)), 

251 Feedback(origin, 

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

253 origin.n_lhs.to_string(), 

254 "array-index"), 

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

256 

257 self.start.add_edge_to(gn_check) 

258 self.start = gn_check 

259 

260 def attach_feasability_check(self, bool_expr, origin): 

261 assert isinstance(bool_expr, smt.Expression) 

262 assert bool_expr.sort is smt.BUILTIN_BOOLEAN 

263 assert isinstance(origin, Expression) 

264 assert not self.functional 

265 

266 if not self.emit_checks: 

267 return 

268 

269 # Attach new graph node advance start 

270 gn_check = graph.Check(self.graph) 

271 gn_check.add_goal(bool_expr, 

272 Feedback(origin, 

273 "expression is always true", 

274 "always-true", 

275 expect_unsat = False), 

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

277 self.start.add_edge_to(gn_check) 

278 

279 def attach_assumption(self, bool_expr): 

280 assert isinstance(bool_expr, smt.Expression) 

281 assert bool_expr.sort is smt.BUILTIN_BOOLEAN 

282 assert not self.functional 

283 

284 # Attach new graph node advance start 

285 gn_ass = graph.Assumption(self.graph) 

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

287 self.start.add_edge_to(gn_ass) 

288 self.start = gn_ass 

289 

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

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

292 assert isinstance(sym, smt.Constant) 

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

294 assert not self.functional 

295 

296 # Attach new graph node advance start 

297 gn_decl = graph.Assumption(self.graph) 

298 gn_decl.add_statement( 

299 smt.Constant_Declaration( 

300 symbol = sym, 

301 value = value, 

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

303 node.location.to_string()), 

304 relevant = False)) 

305 self.start.add_edge_to(gn_decl) 

306 self.start = gn_decl 

307 

308 def attach_empty_assumption(self): 

309 assert not self.functional 

310 

311 # Attach new graph node advance start 

312 gn_decl = graph.Assumption(self.graph) 

313 self.start.add_edge_to(gn_decl) 

314 self.start = gn_decl 

315 

316 def analyze(self): 

317 try: 

318 self.checks_on_composite_type(self.n_ctyp) 

319 except Unsupported as exc: # pragma: no cover 

320 self.mh.warning(exc.location, 

321 exc.message) 

322 

323 def checks_on_composite_type(self, n_ctyp): 

324 assert isinstance(n_ctyp, Composite_Type) 

325 

326 # Create node for global declarations 

327 gn_locals = graph.Assumption(self.graph) 

328 self.start.add_edge_to(gn_locals) 

329 self.start = gn_locals 

330 self.preamble = gn_locals 

331 

332 # Create local variables 

333 for n_component in n_ctyp.all_components(): 

334 self.tr_component_decl(n_component, self.start) 

335 

336 # Create paths for checks in two phases: 

337 # Phase A ("at declaration"): checks that do not follow any 

338 # record or union reference via field access. These are fully 

339 # self-contained and are analyzed first. 

340 # Phase B ("after references"): checks that dereference at 

341 # least one record or union reference. They run after Phase A 

342 # so that knowledge accumulated from Phase A fatal checks is 

343 # available. 

344 for phase in (False, True): 

345 for n_check in n_ctyp.iter_checks(): 

346 if n_check.uses_field_access != phase: 

347 continue 

348 current_start = self.start 

349 self.tr_check(n_check) 

350 

351 # Only fatal checks contribute to the total knowledge 

352 if n_check.severity != "fatal": 

353 self.start = current_start 

354 

355 # Emit debug graph 

356 if self.debug: # pragma: no cover 

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

358 input = self.graph.debug_render_dot(), 

359 check = True, 

360 encoding = "UTF-8") 

361 

362 # Generate VCs 

363 self.vcg.generate() 

364 

365 # Solve VCs and provide feedback 

366 nok_feasibility_checks = [] 

367 ok_feasibility_checks = set() 

368 nok_validity_checks = set() 

369 

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

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

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

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

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

375 

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

377 # again on a different path 

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

379 vc["feedback"] in nok_validity_checks: 

380 continue 

381 

382 solver = CVC5_Solver() 

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

384 solver.set_solver_option(name, value) 

385 

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

387 

388 message = vc["feedback"].message 

389 if self.debug: # pragma: no cover 

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

391 

392 if vc["feedback"].expect_unsat: 

393 if status != "unsat": 

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

395 message, 

396 vc["feedback"].kind, 

397 self.create_counterexample(status, 

398 values)) 

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

400 else: 

401 if status == "unsat": 

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

403 else: 

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

405 

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

407 # consistent 

408 for feedback in nok_feasibility_checks: 

409 if feedback not in ok_feasibility_checks: 

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

411 feedback.message, 

412 feedback.kind) 

413 ok_feasibility_checks.add(feedback) 

414 

415 def create_counterexample(self, status, values): 

416 rv = [ 

417 "example %s triggering error:" % 

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

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

420 ] 

421 

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

423 id_value = self.tr_component_value_name(n_component) 

424 id_valid = self.tr_component_valid_name(n_component) 

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

426 id_valid not in values): 

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

428 elif values.get(id_valid): 

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

430 (n_component.name, 

431 self.value_to_trlc(n_component.n_typ, 

432 values[id_value]))) 

433 else: 

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

435 

436 rv.append(" }") 

437 if status == "unknown": 

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

439 return "\n".join(rv) 

440 

441 def fraction_to_decimal_string(self, num, den): 

442 assert isinstance(num, int) 

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

444 

445 tmp = den 

446 if tmp > 2: 

447 while tmp > 1: 

448 if tmp % 2 == 0: 

449 tmp = tmp // 2 

450 elif tmp % 5 == 0: 

451 tmp = tmp // 5 

452 else: 

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

454 

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

456 

457 i = abs(num) % den 

458 j = den 

459 

460 if i > 0: 

461 rv += "." 

462 while i > 0: 

463 i *= 10 

464 rv += str(i // j) 

465 i = i % j 

466 else: 

467 rv += ".0" 

468 

469 if num < 0: 

470 return "-" + rv 

471 else: 

472 return rv 

473 

474 def value_to_trlc(self, n_typ, value): 

475 assert isinstance(n_typ, Type) 

476 

477 if isinstance(n_typ, Builtin_Integer): 

478 return str(value) 

479 

480 elif isinstance(n_typ, Builtin_Decimal): 

481 if isinstance(value, Fraction): 

482 num, den = value.as_integer_ratio() 

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

484 return self.fraction_to_decimal_string(num, den) 

485 else: 

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

487 else: 

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

489 

490 elif isinstance(n_typ, Builtin_Boolean): 

491 return "true" if value else "false" 

492 

493 elif isinstance(n_typ, Enumeration_Type): 

494 return n_typ.name + "." + value 

495 

496 elif isinstance(n_typ, Builtin_String): 

497 if "\n" in value: 

498 return "'''%s'''" % value 

499 else: 

500 return '"%s"' % value 

501 

502 elif isinstance(n_typ, (Record_Type, Union_Type)): 

503 # lobster-trace: LRM.Union_Type_Equality 

504 if value < 0: 

505 instance_id = value * -2 - 1 

506 else: 

507 instance_id = value * 2 

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

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

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

511 else: 

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

513 n_typ.name, 

514 instance_id) 

515 else: 

516 return "instance_%i" % instance_id 

517 

518 elif isinstance(n_typ, Tuple_Type): 

519 parts = [] 

520 for n_item in n_typ.iter_sequence(): 

521 if isinstance(n_item, Composite_Component): 

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

523 parts.pop() 

524 break 

525 parts.append( 

526 self.value_to_trlc(n_item.n_typ, 

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

528 

529 else: 

530 assert isinstance(n_item, Separator) 

531 sep_text = { 

532 "AT" : "@", 

533 "COLON" : ":", 

534 "SEMICOLON" : ";" 

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

536 parts.append(sep_text) 

537 

538 if n_typ.has_separators(): 

539 return "".join(parts) 

540 else: 

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

542 

543 elif isinstance(n_typ, Array_Type): 

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

545 item) 

546 for item in value) 

547 

548 else: # pragma: no cover 

549 self.flag_unsupported(n_typ, 

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

551 

552 def tr_component_value_name(self, n_component): 

553 return n_component.member_of.fully_qualified_name() + \ 

554 "." + n_component.name + ".value" 

555 

556 def tr_component_valid_name(self, n_component): 

557 return n_component.member_of.fully_qualified_name() + \ 

558 "." + n_component.name + ".valid" 

559 

560 def emit_tuple_constraints(self, n_tuple, s_sym): 

561 assert isinstance(n_tuple, Tuple_Type) 

562 assert isinstance(s_sym, smt.Constant) 

563 

564 old_functional, self.functional = self.functional, True 

565 self.tuple_base[n_tuple] = s_sym 

566 

567 constraints = [] 

568 

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

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

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

572 # record. 

573 

574 for n_check in n_tuple.iter_checks(): 

575 if n_check.severity == "warning": 

576 continue 

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

578 # truth here. 

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

580 constraints.append(c_value) 

581 

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

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

584 

585 components = n_tuple.all_components() 

586 for i, component in enumerate(components): 

587 if component.optional: 

588 condition = smt.Boolean_Negation( 

589 smt.Record_Access(s_sym, 

590 component.name + ".valid")) 

591 consequences = [ 

592 smt.Boolean_Negation( 

593 smt.Record_Access(s_sym, 

594 c.name + ".valid")) 

595 for c in components[i + 1:] 

596 ] 

597 if len(consequences) == 0: 

598 break 

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

600 consequence = consequences[0] 

601 else: 

602 consequence = smt.Conjunction(*consequences) 

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

604 

605 del self.tuple_base[n_tuple] 

606 self.functional = old_functional 

607 

608 for cons in constraints: 

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

610 

611 def tr_component_decl(self, n_component, gn_locals): 

612 assert isinstance(n_component, Composite_Component) 

613 assert isinstance(gn_locals, graph.Assumption) 

614 

615 if isinstance(self.n_ctyp, Record_Type): 

616 frozen = self.n_ctyp.is_frozen(n_component) 

617 else: 

618 frozen = False 

619 

620 id_value = self.tr_component_value_name(n_component) 

621 s_sort = self.tr_type(n_component.n_typ) 

622 s_sym = smt.Constant(s_sort, id_value) 

623 if frozen: 

624 old_functional, self.functional = self.functional, True 

625 s_val, _ = self.tr_expression( 

626 self.n_ctyp.get_freezing_expression(n_component)) 

627 self.functional = old_functional 

628 else: 

629 s_val = None 

630 s_decl = smt.Constant_Declaration( 

631 symbol = s_sym, 

632 value = s_val, 

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

634 n_component.name, 

635 n_component.location.to_string()), 

636 relevant = True) 

637 gn_locals.add_statement(s_decl) 

638 self.constants[id_value] = s_sym 

639 

640 if isinstance(n_component.n_typ, Tuple_Type): 

641 self.emit_tuple_constraints(n_component.n_typ, s_sym) 

642 

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

644 # length 

645 if isinstance(n_component.n_typ, Array_Type): 

646 if n_component.n_typ.lower_bound > 0: 

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

648 gn_locals.add_statement( 

649 smt.Assertion( 

650 smt.Comparison(">=", 

651 smt.Sequence_Length(s_sym), 

652 s_lower))) 

653 

654 if n_component.n_typ.upper_bound is not None: 

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

656 gn_locals.add_statement( 

657 smt.Assertion( 

658 smt.Comparison("<=", 

659 smt.Sequence_Length(s_sym), 

660 s_upper))) 

661 

662 id_valid = self.tr_component_valid_name(n_component) 

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

664 s_val = (None 

665 if n_component.optional and not frozen 

666 else smt.Boolean_Literal(True)) 

667 s_decl = smt.Constant_Declaration( 

668 symbol = s_sym, 

669 value = s_val, 

670 relevant = True) 

671 gn_locals.add_statement(s_decl) 

672 self.constants[id_valid] = s_sym 

673 

674 def tr_type(self, n_type): 

675 assert isinstance(n_type, Type) 

676 

677 if isinstance(n_type, Builtin_Boolean): 

678 return smt.BUILTIN_BOOLEAN 

679 

680 elif isinstance(n_type, Builtin_Integer): 

681 return smt.BUILTIN_INTEGER 

682 

683 elif isinstance(n_type, Builtin_Decimal): 

684 return smt.BUILTIN_REAL 

685 

686 elif isinstance(n_type, Builtin_String): 

687 return smt.BUILTIN_STRING 

688 

689 elif isinstance(n_type, Enumeration_Type): 

690 if n_type not in self.enumerations: 

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

692 "." + n_type.name) 

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

694 s_sort.add_literal(n_lit.name) 

695 self.enumerations[n_type] = s_sort 

696 self.start.add_statement( 

697 smt.Enumeration_Declaration( 

698 s_sort, 

699 "enumeration %s from %s" % ( 

700 n_type.name, 

701 n_type.location.to_string()))) 

702 return self.enumerations[n_type] 

703 

704 elif isinstance(n_type, Tuple_Type): 

705 if n_type not in self.tuples: 

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

707 "." + n_type.name) 

708 for n_component in n_type.all_components(): 

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

710 self.tr_type(n_component.n_typ)) 

711 if n_component.optional: 

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

713 smt.BUILTIN_BOOLEAN) 

714 self.tuples[n_type] = s_sort 

715 self.start.add_statement( 

716 smt.Record_Declaration( 

717 s_sort, 

718 "tuple %s from %s" % ( 

719 n_type.name, 

720 n_type.location.to_string()))) 

721 

722 return self.tuples[n_type] 

723 

724 elif isinstance(n_type, Array_Type): 

725 if n_type not in self.arrays: 

726 s_element_sort = self.tr_type(n_type.element_type) 

727 s_sequence = smt.Sequence_Sort(s_element_sort) 

728 self.arrays[n_type] = s_sequence 

729 

730 return self.arrays[n_type] 

731 

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

733 # lobster-trace: LRM.union_type 

734 # Record and union references are modelled as a free 

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

736 # uninterpreted function. Some of these have special 

737 # meaning: 

738 # 0 - the null reference 

739 # 1 - the self reference 

740 # anything else - uninterpreted 

741 return smt.BUILTIN_INTEGER 

742 

743 else: # pragma: no cover 

744 self.flag_unsupported(n_type) 

745 

746 def tr_check(self, n_check): 

747 assert isinstance(n_check, Check) 

748 

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

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

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

752 # failed. 

753 if n_check.n_type is not self.n_ctyp: 

754 old_emit, self.emit_checks = self.emit_checks, False 

755 

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

757 self.attach_validity_check(valid, n_check.n_expr) 

758 self.attach_feasability_check(value, n_check.n_expr) 

759 self.attach_assumption(value) 

760 

761 if n_check.n_type is not self.n_ctyp: 

762 self.emit_checks = old_emit 

763 

764 def tr_expression(self, n_expr): 

765 value = None 

766 

767 if isinstance(n_expr, Name_Reference): 

768 return self.tr_name_reference(n_expr) 

769 

770 elif isinstance(n_expr, Unary_Expression): 

771 return self.tr_unary_expression(n_expr) 

772 

773 elif isinstance(n_expr, Binary_Expression): 

774 return self.tr_binary_expression(n_expr) 

775 

776 elif isinstance(n_expr, Range_Test): 

777 return self.tr_range_test(n_expr) 

778 

779 elif isinstance(n_expr, OneOf_Expression): 

780 return self.tr_oneof_test(n_expr) 

781 

782 elif isinstance(n_expr, Conditional_Expression): 

783 if self.functional: 

784 return self.tr_conditional_expression_functional(n_expr) 

785 else: 

786 return self.tr_conditional_expression(n_expr) 

787 

788 elif isinstance(n_expr, Null_Literal): 

789 return None, smt.Boolean_Literal(False) 

790 

791 elif isinstance(n_expr, Boolean_Literal): 

792 value = smt.Boolean_Literal(n_expr.value) 

793 

794 elif isinstance(n_expr, Integer_Literal): 

795 value = smt.Integer_Literal(n_expr.value) 

796 

797 elif isinstance(n_expr, Decimal_Literal): 

798 value = smt.Real_Literal(n_expr.value) 

799 

800 elif isinstance(n_expr, Enumeration_Literal): 

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

802 n_expr.value.name) 

803 

804 elif isinstance(n_expr, String_Literal): 

805 value = smt.String_Literal(n_expr.value) 

806 

807 elif isinstance(n_expr, Quantified_Expression): 

808 return self.tr_quantified_expression(n_expr) 

809 

810 elif isinstance(n_expr, Field_Access_Expression): 

811 return self.tr_field_access_expression(n_expr) 

812 

813 else: # pragma: no cover 

814 self.flag_unsupported(n_expr) 

815 

816 return value, smt.Boolean_Literal(True) 

817 

818 def tr_name_reference(self, n_ref): 

819 assert isinstance(n_ref, Name_Reference) 

820 

821 if isinstance(n_ref.entity, Composite_Component): 

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

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

824 if n_ref.entity.optional: 

825 s_valid = smt.Record_Access(sym, 

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

827 else: 

828 s_valid = smt.Boolean_Literal(True) 

829 s_value = smt.Record_Access(sym, 

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

831 return s_value, s_valid 

832 

833 else: 

834 id_value = self.tr_component_value_name(n_ref.entity) 

835 id_valid = self.tr_component_valid_name(n_ref.entity) 

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

837 

838 else: 

839 assert isinstance(n_ref.entity, Quantified_Variable) 

840 if n_ref.entity in self.qe_vars: 

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

842 else: 

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

844 

845 def tr_unary_expression(self, n_expr): 

846 assert isinstance(n_expr, Unary_Expression) 

847 

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

849 if not self.functional: 

850 self.attach_validity_check(operand_valid, n_expr.n_operand) 

851 

852 sym_value = None 

853 

854 if n_expr.operator == Unary_Operator.MINUS: 

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

856 sym_value = smt.Unary_Int_Arithmetic_Op("-", 

857 operand_value) 

858 else: 

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

860 sym_value = smt.Unary_Real_Arithmetic_Op("-", 

861 operand_value) 

862 

863 elif n_expr.operator == Unary_Operator.PLUS: 

864 sym_value = operand_value 

865 

866 elif n_expr.operator == Unary_Operator.LOGICAL_NOT: 

867 sym_value = smt.Boolean_Negation(operand_value) 

868 

869 elif n_expr.operator == Unary_Operator.ABSOLUTE_VALUE: 

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

871 sym_value = smt.Unary_Int_Arithmetic_Op("abs", 

872 operand_value) 

873 

874 else: 

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

876 sym_value = smt.Unary_Real_Arithmetic_Op("abs", 

877 operand_value) 

878 

879 elif n_expr.operator == Unary_Operator.STRING_LENGTH: 

880 sym_value = smt.String_Length(operand_value) 

881 

882 elif n_expr.operator == Unary_Operator.ARRAY_LENGTH: 

883 sym_value = smt.Sequence_Length(operand_value) 

884 

885 elif n_expr.operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

886 sym_value = smt.Conversion_To_Real(operand_value) 

887 

888 elif n_expr.operator == Unary_Operator.CONVERSION_TO_INT: 

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

890 

891 else: 

892 self.mh.ice_loc(n_expr, 

893 "unexpected unary operator %s" % 

894 n_expr.operator.name) 

895 

896 return self.create_return(n_expr, sym_value) 

897 

898 def tr_binary_expression(self, n_expr): 

899 assert isinstance(n_expr, Binary_Expression) 

900 

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

902 # deal with them first and then exit. 

903 if n_expr.operator in (Binary_Operator.COMP_EQ, 

904 Binary_Operator.COMP_NEQ): 

905 return self.tr_op_equality(n_expr) 

906 

907 elif n_expr.operator == Binary_Operator.LOGICAL_IMPLIES: 

908 return self.tr_op_implication(n_expr) 

909 

910 elif n_expr.operator == Binary_Operator.LOGICAL_AND: 

911 return self.tr_op_and(n_expr) 

912 

913 elif n_expr.operator == Binary_Operator.LOGICAL_OR: 

914 return self.tr_op_or(n_expr) 

915 

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

917 # obtain the values of both sides now. 

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

919 if not self.functional: 

920 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

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

922 if not self.functional: 

923 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

924 sym_value = None 

925 

926 if n_expr.operator == Binary_Operator.LOGICAL_XOR: 

927 sym_value = smt.Exclusive_Disjunction(lhs_value, rhs_value) 

928 

929 elif n_expr.operator in (Binary_Operator.PLUS, 

930 Binary_Operator.MINUS, 

931 Binary_Operator.TIMES, 

932 Binary_Operator.DIVIDE, 

933 Binary_Operator.REMAINDER): 

934 

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

936 assert n_expr.operator == Binary_Operator.PLUS 

937 sym_value = smt.String_Concatenation(lhs_value, rhs_value) 

938 

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

940 if n_expr.operator in (Binary_Operator.DIVIDE, 

941 Binary_Operator.REMAINDER): 

942 self.attach_int_division_check(rhs_value, n_expr) 

943 

944 smt_op = { 

945 Binary_Operator.PLUS : "+", 

946 Binary_Operator.MINUS : "-", 

947 Binary_Operator.TIMES : "*", 

948 Binary_Operator.DIVIDE : "floor_div", 

949 Binary_Operator.REMAINDER : "ada_remainder", 

950 }[n_expr.operator] 

951 

952 sym_value = smt.Binary_Int_Arithmetic_Op(smt_op, 

953 lhs_value, 

954 rhs_value) 

955 

956 else: 

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

958 if n_expr.operator == Binary_Operator.DIVIDE: 

959 self.attach_real_division_check(rhs_value, n_expr) 

960 

961 smt_op = { 

962 Binary_Operator.PLUS : "+", 

963 Binary_Operator.MINUS : "-", 

964 Binary_Operator.TIMES : "*", 

965 Binary_Operator.DIVIDE : "/", 

966 }[n_expr.operator] 

967 

968 sym_value = smt.Binary_Real_Arithmetic_Op(smt_op, 

969 lhs_value, 

970 rhs_value) 

971 

972 elif n_expr.operator in (Binary_Operator.COMP_LT, 

973 Binary_Operator.COMP_LEQ, 

974 Binary_Operator.COMP_GT, 

975 Binary_Operator.COMP_GEQ): 

976 smt_op = { 

977 Binary_Operator.COMP_LT : "<", 

978 Binary_Operator.COMP_LEQ : "<=", 

979 Binary_Operator.COMP_GT : ">", 

980 Binary_Operator.COMP_GEQ : ">=", 

981 }[n_expr.operator] 

982 

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

984 

985 elif n_expr.operator in (Binary_Operator.STRING_CONTAINS, 

986 Binary_Operator.STRING_STARTSWITH, 

987 Binary_Operator.STRING_ENDSWITH): 

988 

989 smt_op = { 

990 Binary_Operator.STRING_CONTAINS : "contains", 

991 Binary_Operator.STRING_STARTSWITH : "prefixof", 

992 Binary_Operator.STRING_ENDSWITH : "suffixof" 

993 } 

994 

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

996 # other way around than in TRLC. 

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

998 rhs_value, 

999 lhs_value) 

1000 

1001 elif n_expr.operator == Binary_Operator.STRING_REGEX: 

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

1003 assert isinstance(rhs_evaluation, str) 

1004 

1005 sym_value = smt.Function_Application( 

1006 self.get_uf_matches(), 

1007 lhs_value, 

1008 smt.String_Literal(rhs_evaluation)) 

1009 

1010 elif n_expr.operator == Binary_Operator.INDEX: 

1011 self.attach_index_check(lhs_value, rhs_value, n_expr) 

1012 sym_value = smt.Sequence_Index(lhs_value, rhs_value) 

1013 

1014 elif n_expr.operator == Binary_Operator.ARRAY_CONTAINS: 

1015 sym_value = smt.Sequence_Contains(rhs_value, lhs_value) 

1016 

1017 elif n_expr.operator == Binary_Operator.POWER: 

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

1019 # integer 

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

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

1022 

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

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

1025 sym_value = smt.Integer_Literal(1) 

1026 else: 

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

1028 sym_value = smt.Real_Literal(1) 

1029 

1030 else: 

1031 sym_value = lhs_value 

1032 for _ in range(1, static_value): 

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

1034 sym_value = smt.Binary_Int_Arithmetic_Op("*", 

1035 sym_value, 

1036 lhs_value) 

1037 else: 

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

1039 sym_value = smt.Binary_Real_Arithmetic_Op("*", 

1040 sym_value, 

1041 lhs_value) 

1042 

1043 else: # pragma: no cover 

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

1045 

1046 return self.create_return(n_expr, sym_value) 

1047 

1048 def tr_range_test(self, n_expr): 

1049 assert isinstance(n_expr, Range_Test) 

1050 

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

1052 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

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

1054 self.attach_validity_check(lower_valid, n_expr.n_lower) 

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

1056 self.attach_validity_check(upper_valid, n_expr.n_upper) 

1057 

1058 sym_value = smt.Conjunction( 

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

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

1061 

1062 return self.create_return(n_expr, sym_value) 

1063 

1064 def tr_oneof_test(self, n_expr): 

1065 assert isinstance(n_expr, OneOf_Expression) 

1066 

1067 choices = [] 

1068 for n_choice in n_expr.choices: 

1069 c_value, c_valid = self.tr_expression(n_choice) 

1070 self.attach_validity_check(c_valid, n_choice) 

1071 choices.append(c_value) 

1072 

1073 negated_choices = [smt.Boolean_Negation(c) 

1074 for c in choices] 

1075 

1076 # pylint: disable=consider-using-enumerate 

1077 

1078 if len(choices) == 1: 

1079 result = choices[0] 

1080 elif len(choices) == 2: 

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

1082 else: 

1083 assert len(choices) >= 3 

1084 values = [] 

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

1086 sequence = [] 

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

1088 if other_id == choice_id: 

1089 sequence.append(choices[other_id]) 

1090 else: 

1091 sequence.append(negated_choices[other_id]) 

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

1093 result = smt.Disjunction(*values) 

1094 

1095 return self.create_return(n_expr, result) 

1096 

1097 def tr_conditional_expression_functional(self, n_expr): 

1098 assert isinstance(n_expr, Conditional_Expression) 

1099 

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

1101 for n_action in reversed(n_expr.actions): 

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

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

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

1105 

1106 return self.create_return(n_expr, s_result) 

1107 

1108 def tr_conditional_expression(self, n_expr): 

1109 assert isinstance(n_expr, Conditional_Expression) 

1110 assert not self.functional 

1111 

1112 gn_end = graph.Node(self.graph) 

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

1114 self.new_temp_name()) 

1115 

1116 for n_action in n_expr.actions: 

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

1118 self.attach_validity_check(test_valid, n_action.n_cond) 

1119 current_start = self.start 

1120 

1121 # Create path where action is true 

1122 self.attach_assumption(test_value) 

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

1124 self.attach_validity_check(res_valid, n_action.n_expr) 

1125 self.attach_temp_declaration(n_action, 

1126 sym_result, 

1127 res_value) 

1128 self.start.add_edge_to(gn_end) 

1129 

1130 # Reset to test and proceed with the other actions 

1131 self.start = current_start 

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

1133 

1134 # Finally execute the else part 

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

1136 self.attach_validity_check(res_valid, n_expr.else_expr) 

1137 self.attach_temp_declaration(n_expr, 

1138 sym_result, 

1139 res_value) 

1140 self.start.add_edge_to(gn_end) 

1141 

1142 # And join 

1143 self.start = gn_end 

1144 return sym_result, smt.Boolean_Literal(True) 

1145 

1146 def tr_op_implication(self, n_expr): 

1147 assert isinstance(n_expr, Binary_Expression) 

1148 assert n_expr.operator == Binary_Operator.LOGICAL_IMPLIES 

1149 

1150 if self.functional: 

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

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

1153 return self.create_return(n_expr, 

1154 smt.Implication(lhs_value, rhs_value)) 

1155 

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

1157 # Emit VC for validity 

1158 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1159 

1160 # Split into two paths. 

1161 current_start = self.start 

1162 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, 

1163 self.new_temp_name()) 

1164 gn_end = graph.Node(self.graph) 

1165 

1166 ### 1: Implication is not valid 

1167 self.start = current_start 

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

1169 self.attach_temp_declaration(n_expr, 

1170 sym_result, 

1171 smt.Boolean_Literal(True)) 

1172 self.start.add_edge_to(gn_end) 

1173 

1174 ### 2: Implication is valid. 

1175 self.start = current_start 

1176 self.attach_assumption(lhs_value) 

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

1178 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1179 self.attach_temp_declaration(n_expr, 

1180 sym_result, 

1181 rhs_value) 

1182 self.start.add_edge_to(gn_end) 

1183 

1184 # Join paths 

1185 self.start = gn_end 

1186 

1187 return sym_result, smt.Boolean_Literal(True) 

1188 

1189 def tr_op_and(self, n_expr): 

1190 assert isinstance(n_expr, Binary_Expression) 

1191 assert n_expr.operator == Binary_Operator.LOGICAL_AND 

1192 

1193 if self.functional: 

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

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

1196 return self.create_return(n_expr, 

1197 smt.Conjunction(lhs_value, rhs_value)) 

1198 

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

1200 # Emit VC for validity 

1201 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1202 

1203 # Split into two paths. 

1204 current_start = self.start 

1205 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, 

1206 self.new_temp_name()) 

1207 gn_end = graph.Node(self.graph) 

1208 

1209 ### 1: LHS is not true 

1210 self.start = current_start 

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

1212 self.attach_temp_declaration(n_expr, 

1213 sym_result, 

1214 smt.Boolean_Literal(False)) 

1215 self.start.add_edge_to(gn_end) 

1216 

1217 ### 2: LHS is true 

1218 self.start = current_start 

1219 self.attach_assumption(lhs_value) 

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

1221 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1222 self.attach_temp_declaration(n_expr, 

1223 sym_result, 

1224 rhs_value) 

1225 self.start.add_edge_to(gn_end) 

1226 

1227 # Join paths 

1228 self.start = gn_end 

1229 

1230 return sym_result, smt.Boolean_Literal(True) 

1231 

1232 def tr_op_or(self, n_expr): 

1233 assert isinstance(n_expr, Binary_Expression) 

1234 assert n_expr.operator == Binary_Operator.LOGICAL_OR 

1235 

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

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

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

1239 return self.create_return(n_expr, 

1240 smt.Disjunction(lhs_value, rhs_value)) 

1241 

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

1243 # Emit VC for validity 

1244 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1245 

1246 # Split into two paths. 

1247 current_start = self.start 

1248 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, 

1249 self.new_temp_name()) 

1250 gn_end = graph.Node(self.graph) 

1251 

1252 ### 1: LHS is true 

1253 self.start = current_start 

1254 self.attach_assumption(lhs_value) 

1255 self.attach_temp_declaration(n_expr, 

1256 sym_result, 

1257 smt.Boolean_Literal(True)) 

1258 self.start.add_edge_to(gn_end) 

1259 

1260 ### 2: LHS is not true 

1261 self.start = current_start 

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

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

1264 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1265 self.attach_temp_declaration(n_expr, 

1266 sym_result, 

1267 rhs_value) 

1268 self.start.add_edge_to(gn_end) 

1269 

1270 # Join paths 

1271 self.start = gn_end 

1272 

1273 return sym_result, smt.Boolean_Literal(True) 

1274 

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

1276 assert isinstance(n_component, Composite_Component) 

1277 assert isinstance(lhs, smt.Expression) 

1278 assert isinstance(rhs, smt.Expression) 

1279 

1280 value_lhs = smt.Record_Access(lhs, 

1281 n_component.name + ".value") 

1282 value_rhs = smt.Record_Access(rhs, 

1283 n_component.name + ".value") 

1284 valid_equal = self.tr_core_equality(n_component.n_typ, 

1285 value_lhs, 

1286 value_rhs) 

1287 

1288 if not n_component.optional: 

1289 return valid_equal 

1290 

1291 valid_lhs = smt.Record_Access(lhs, 

1292 n_component.name + ".valid") 

1293 valid_rhs = smt.Record_Access(rhs, 

1294 n_component.name + ".valid") 

1295 

1296 return smt.Conjunction( 

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

1298 smt.Implication(valid_lhs, valid_equal)) 

1299 

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

1301 assert isinstance(n_typ, Type) 

1302 assert isinstance(lhs, smt.Expression) 

1303 assert isinstance(rhs, smt.Expression) 

1304 

1305 if isinstance(n_typ, Tuple_Type): 

1306 parts = [] 

1307 for n_component in n_typ.all_components(): 

1308 parts.append( 

1309 self.tr_core_equality_tuple_component(n_component, 

1310 lhs, 

1311 rhs)) 

1312 

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

1314 return smt.Boolean_Literal(True) 

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

1316 return parts[0] 

1317 else: 

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

1319 for part in parts[2:]: 

1320 result = smt.Conjunction(result, part) 

1321 return result 

1322 

1323 else: 

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

1325 

1326 def tr_op_equality(self, n_expr): 

1327 assert isinstance(n_expr, Binary_Expression) 

1328 assert n_expr.operator in (Binary_Operator.COMP_EQ, 

1329 Binary_Operator.COMP_NEQ) 

1330 

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

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

1333 

1334 if lhs_value is None: 

1335 comp_typ = n_expr.n_rhs.typ 

1336 else: 

1337 comp_typ = n_expr.n_lhs.typ 

1338 

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

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

1341 result = self.tr_core_equality(comp_typ, 

1342 lhs_value, 

1343 rhs_value) 

1344 

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

1346 # This is null == null, so true 

1347 result = smt.Boolean_Literal(True) 

1348 

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

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

1351 result = smt.Boolean_Negation(rhs_valid) 

1352 

1353 elif rhs_value is None: 

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

1355 result = smt.Boolean_Negation(lhs_valid) 

1356 

1357 else: 

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

1359 result = smt.Conjunction( 

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

1361 smt.Implication(lhs_valid, 

1362 self.tr_core_equality(comp_typ, 

1363 lhs_value, 

1364 rhs_value))) 

1365 

1366 if n_expr.operator == Binary_Operator.COMP_NEQ: 

1367 result = smt.Boolean_Negation(result) 

1368 

1369 return self.create_return(n_expr, result) 

1370 

1371 def tr_quantified_expression(self, n_expr): 

1372 assert isinstance(n_expr, Quantified_Expression) 

1373 

1374 # Nested quantifiers are not supported yet 

1375 if self.functional: # pragma: no cover 

1376 self.flag_unsupported(n_expr, 

1377 "functional evaluation of quantifier") 

1378 

1379 # TRLC quantifier 

1380 # (forall x in arr_name => body) 

1381 # 

1382 # SMT quantifier 

1383 # (forall ((i Int)) 

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

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

1386 # 

1387 # There is an alternative which is: 

1388 # (forall ((element ElementSort)) 

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

1390 # (... element ...) 

1391 # 

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

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

1394 # value of some sequence member. 

1395 

1396 # Evaluate subject first and creat a null check 

1397 s_subject_value, s_subject_valid = \ 

1398 self.tr_name_reference(n_expr.n_source) 

1399 self.attach_validity_check(s_subject_valid, n_expr.n_source) 

1400 

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

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

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

1404 # it's valid). 

1405 current_start = self.start 

1406 self.attach_empty_assumption() 

1407 src_typ = n_expr.n_source.typ 

1408 assert isinstance(src_typ, Array_Type) 

1409 s_qe_index = smt.Constant(smt.BUILTIN_INTEGER, 

1410 self.new_temp_name()) 

1411 self.start.add_statement( 

1412 smt.Constant_Declaration( 

1413 symbol = s_qe_index, 

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

1415 (n_expr.to_string(), 

1416 n_expr.location.to_string())))) 

1417 self.start.add_statement( 

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

1419 s_qe_index, 

1420 smt.Integer_Literal(0)))) 

1421 self.start.add_statement( 

1422 smt.Assertion( 

1423 smt.Comparison("<", 

1424 s_qe_index, 

1425 smt.Sequence_Length(s_subject_value)))) 

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

1427 self.new_temp_name()) 

1428 self.start.add_statement( 

1429 smt.Constant_Declaration( 

1430 symbol = s_qe_sym, 

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

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

1433 (n_expr.to_string(), 

1434 n_expr.location.to_string())))) 

1435 self.qe_vars[n_expr.n_var] = s_qe_sym 

1436 

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

1438 self.attach_validity_check(b_valid, n_expr.n_expr) 

1439 

1440 self.start = current_start 

1441 del self.qe_vars[n_expr.n_var] 

1442 

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

1444 # raise exception. Asserting the actual value of the 

1445 # quantifier is more awkward. 

1446 

1447 s_q_idx = smt.Bound_Variable(smt.BUILTIN_INTEGER, 

1448 self.new_temp_name()) 

1449 s_q_sym = smt.Sequence_Index(s_subject_value, s_q_idx) 

1450 self.bound_vars[n_expr.n_var] = s_q_sym 

1451 

1452 temp, self.functional = self.functional, True 

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

1454 self.functional = temp 

1455 

1456 bounds_expr = smt.Conjunction( 

1457 smt.Comparison(">=", 

1458 s_q_idx, 

1459 smt.Integer_Literal(0)), 

1460 smt.Comparison("<", 

1461 s_q_idx, 

1462 smt.Sequence_Length(s_subject_value))) 

1463 if n_expr.universal: 

1464 value = smt.Quantifier( 

1465 "forall", 

1466 [s_q_idx], 

1467 smt.Implication(bounds_expr, b_value)) 

1468 else: 

1469 value = smt.Quantifier( 

1470 "exists", 

1471 [s_q_idx], 

1472 smt.Conjunction(bounds_expr, b_value)) 

1473 

1474 return value, smt.Boolean_Literal(True) 

1475 

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

1477 components): 

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

1479 for dereferencing integer-encoded references. 

1480 

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

1482 :param sort_name: name for the SMT Record sort 

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

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

1485 :returns: (record_sort, to_record_uf) 

1486 """ 

1487 if type_key in self.records: 

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

1489 

1490 record_sort = smt.Record(sort_name) 

1491 for field_name, field_sort, needs_valid in components: 

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

1493 if needs_valid: 

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

1495 smt.BUILTIN_BOOLEAN) 

1496 self.records[type_key] = record_sort 

1497 self.preamble.add_statement( 

1498 smt.Record_Declaration( 

1499 record_sort, 

1500 sort_name)) 

1501 

1502 to_record_uf = smt.Function( 

1503 uf_name, record_sort, 

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

1505 self.preamble.add_statement( 

1506 smt.Function_Declaration(to_record_uf)) 

1507 self.uf_records[type_key] = to_record_uf 

1508 

1509 return record_sort, to_record_uf 

1510 

1511 def tr_field_access_expression(self, n_expr): 

1512 assert isinstance(n_expr, Field_Access_Expression) 

1513 

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

1515 prefix_typ = n_expr.n_prefix.typ 

1516 if not self.functional: 

1517 self.attach_validity_check(prefix_valid, n_expr.n_prefix) 

1518 

1519 if isinstance(prefix_typ, Tuple_Type): 

1520 field_value = smt.Record_Access(prefix_value, 

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

1522 if n_expr.n_field.optional: 

1523 field_valid = smt.Record_Access(prefix_value, 

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

1525 else: 

1526 field_valid = smt.Boolean_Literal(True) 

1527 

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

1529 # lobster-trace: LRM.Union_Type_Field_Access 

1530 # lobster-trace: LRM.Union_Type_Partial_Field_Access 

1531 # lobster-trace: LRM.Union_Type_Partial_Field_Null 

1532 # Both Record_Type and Union_Type are represented as 

1533 # integers. We create a record sort with accessible 

1534 # fields and a UF to dereference the integer. 

1535 if isinstance(prefix_typ, Record_Type): 

1536 components = [ 

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

1538 for c in prefix_typ.all_components() 

1539 ] 

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

1541 prefix_typ.name) 

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

1543 prefix_typ.name) 

1544 else: 

1545 field_map = prefix_typ.get_field_map() 

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

1547 for t in prefix_typ.types) 

1548 components = [ 

1549 (name, 

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

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

1552 info["optional_in_any"]) 

1553 for name, info in field_map.items() 

1554 if info["n_typ"] is not None 

1555 ] 

1556 sort_name = "union." + union_id 

1557 uf_name = "access.union." + union_id 

1558 

1559 _, to_record_uf = self._ensure_record_deref( 

1560 sort_name, sort_name, uf_name, components) 

1561 dereference = smt.Function_Application(to_record_uf, 

1562 prefix_value) 

1563 

1564 # Perform the field access on the dereferenced record 

1565 field_value = smt.Record_Access(dereference, 

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

1567 if isinstance(prefix_typ, Union_Type): 

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

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

1570 info["optional_in_any"]) 

1571 else: 

1572 has_valid = n_expr.n_field.optional 

1573 

1574 if has_valid: 

1575 field_valid = smt.Record_Access( 

1576 dereference, 

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

1578 else: 

1579 field_valid = smt.Boolean_Literal(True) 

1580 

1581 else: 

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

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

1584 n_expr.n_prefix.typ.__class__.__name__) 

1585 

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

1587 return field_value, field_valid