Coverage for trlc/vcg.py: 97%

787 statements  

« prev     ^ index     » next       coverage.py v7.10.7, created at 2026-09-04 11:46 +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 

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 

40 CVC5_API_AVAILABLE = True 

41except ImportError: # pragma: no cover 

42 CVC5_API_AVAILABLE = False 

43 

44CVC5_OPTIONS = { 

45 "tlimit-per": 2500, 

46 "seed": 42, 

47 "sat-random-seed": 42, 

48} 

49 

50 

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

52 # lobster-exclude: Not safety relevant 

53 def __init__(self, node, text): 

54 assert isinstance(node, Node) 

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

56 super().__init__() 

57 self.message = "%s not yet supported in VCG" % ( 

58 text if text else node.__class__.__name__ 

59 ) 

60 self.location = node.location 

61 

62 

63class Feedback: 

64 # lobster-exclude: Not safety relevant 

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

66 assert isinstance(node, Expression) 

67 assert isinstance(message, str) 

68 assert isinstance(kind, str) 

69 assert isinstance(expect_unsat, bool) 

70 self.node = node 

71 self.message = message 

72 self.kind = "vcg-" + kind 

73 self.expect_unsat = expect_unsat 

74 

75 

76class VCG: 

77 # lobster-exclude: Not safety relevant 

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

79 assert VCG_AVAILABLE 

80 assert isinstance(mh, Message_Handler) 

81 assert isinstance(n_ctyp, Composite_Type) 

82 assert isinstance(debug, bool) 

83 

84 self.mh = mh 

85 self.n_ctyp = n_ctyp 

86 self.debug = debug 

87 

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

89 

90 self.tmp_id = 0 

91 

92 self.vcg = vcg.VCG() 

93 self.graph = self.vcg.graph 

94 self.start = self.vcg.start 

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

96 self.preamble = None 

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

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

99 

100 self.constants = {} 

101 self.enumerations = {} 

102 self.tuples = {} 

103 self.records = {} 

104 self.arrays = {} 

105 self.bound_vars = {} 

106 self.qe_vars = {} 

107 self.tuple_base = {} 

108 self.uf_records = {} 

109 

110 self.uf_matches = None 

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

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

113 # string theory being used. 

114 

115 self.functional = False 

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

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

118 # expressions and return them. 

119 

120 self.emit_checks = True 

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

122 

123 @staticmethod 

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

125 assert isinstance(node, Node) 

126 raise Unsupported(node, text) 

127 

128 def new_temp_name(self): 

129 self.tmp_id += 1 

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

131 

132 def get_uf_matches(self): 

133 if self.uf_matches is None: 

134 self.uf_matches = smt.Function( 

135 "trlc.matches", 

136 smt.BUILTIN_BOOLEAN, 

137 smt.Bound_Variable(smt.BUILTIN_STRING, "subject"), 

138 smt.Bound_Variable(smt.BUILTIN_STRING, "regex"), 

139 ) 

140 

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

142 # will deal with regex properly). 

143 self.preamble.add_statement(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, self.new_temp_name()) 

160 self.attach_temp_declaration(node, sym_result, s_value) 

161 

162 return sym_result, s_valid 

163 

164 def attach_validity_check(self, bool_expr, origin): 

165 assert isinstance(bool_expr, smt.Expression) 

166 assert bool_expr.sort is smt.BUILTIN_BOOLEAN 

167 assert isinstance(origin, Expression) 

168 assert not self.functional 

169 

170 if not self.emit_checks: 

171 return 

172 

173 # Attach new graph node advance start 

174 if not bool_expr.is_static_true(): 

175 gn_check = graph.Check(self.graph) 

176 gn_check.add_goal( 

177 bool_expr, 

178 Feedback(origin, "expression could be null", "evaluation-of-null"), 

179 "validity check for %s" % origin.to_string(), 

180 ) 

181 self.start.add_edge_to(gn_check) 

182 self.start = gn_check 

183 

184 def attach_int_division_check(self, int_expr, origin): 

185 assert isinstance(int_expr, smt.Expression) 

186 assert int_expr.sort is smt.BUILTIN_INTEGER 

187 assert isinstance(origin, Expression) 

188 assert not self.functional 

189 

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

191 return 

192 

193 # Attach new graph node advance start 

194 gn_check = graph.Check(self.graph) 

195 gn_check.add_goal( 

196 smt.Boolean_Negation(smt.Comparison("=", int_expr, smt.Integer_Literal(0))), 

197 Feedback(origin, "divisor could be 0", "div-by-zero"), 

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

199 ) 

200 self.start.add_edge_to(gn_check) 

201 self.start = gn_check 

202 

203 def attach_real_division_check(self, real_expr, origin): 

204 assert isinstance(real_expr, smt.Expression) 

205 assert real_expr.sort is smt.BUILTIN_REAL 

206 assert isinstance(origin, Expression) 

207 assert not self.functional 

208 

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

210 return 

211 

212 # Attach new graph node advance start 

213 gn_check = graph.Check(self.graph) 

214 gn_check.add_goal( 

215 smt.Boolean_Negation(smt.Comparison("=", real_expr, smt.Real_Literal(0))), 

216 Feedback(origin, "divisor could be 0.0", "div-by-zero"), 

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

218 ) 

219 self.start.add_edge_to(gn_check) 

220 self.start = gn_check 

221 

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

223 assert isinstance(seq_expr, smt.Expression) 

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

225 assert isinstance(index_expr, smt.Expression) 

226 assert index_expr.sort is smt.BUILTIN_INTEGER 

227 assert isinstance(origin, Binary_Expression) 

228 assert origin.operator == Binary_Operator.INDEX 

229 assert not self.functional 

230 

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

232 return 

233 

234 # Attach new graph node advance start 

235 gn_check = graph.Check(self.graph) 

236 gn_check.add_goal( 

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

238 Feedback(origin, "array index could be less than 0", "array-index"), 

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

240 ) 

241 gn_check.add_goal( 

242 smt.Comparison("<", index_expr, smt.Sequence_Length(seq_expr)), 

243 Feedback( 

244 origin, 

245 "array index could be larger than len(%s)" % origin.n_lhs.to_string(), 

246 "array-index", 

247 ), 

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

249 ) 

250 

251 self.start.add_edge_to(gn_check) 

252 self.start = gn_check 

253 

254 def attach_feasability_check(self, bool_expr, origin): 

255 assert isinstance(bool_expr, smt.Expression) 

256 assert bool_expr.sort is smt.BUILTIN_BOOLEAN 

257 assert isinstance(origin, Expression) 

258 assert not self.functional 

259 

260 if not self.emit_checks: 

261 return 

262 

263 # Attach new graph node advance start 

264 gn_check = graph.Check(self.graph) 

265 gn_check.add_goal( 

266 bool_expr, 

267 Feedback( 

268 origin, "expression is always true", "always-true", expect_unsat=False 

269 ), 

270 "feasability check for %s" % origin.to_string(), 

271 ) 

272 self.start.add_edge_to(gn_check) 

273 

274 def attach_assumption(self, bool_expr): 

275 assert isinstance(bool_expr, smt.Expression) 

276 assert bool_expr.sort is smt.BUILTIN_BOOLEAN 

277 assert not self.functional 

278 

279 # Attach new graph node advance start 

280 gn_ass = graph.Assumption(self.graph) 

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

282 self.start.add_edge_to(gn_ass) 

283 self.start = gn_ass 

284 

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

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

287 assert isinstance(sym, smt.Constant) 

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

289 assert not self.functional 

290 

291 # Attach new graph node advance start 

292 gn_decl = graph.Assumption(self.graph) 

293 gn_decl.add_statement( 

294 smt.Constant_Declaration( 

295 symbol=sym, 

296 value=value, 

297 comment="result of %s at %s" 

298 % (node.to_string(), node.location.to_string()), 

299 relevant=False, 

300 ) 

301 ) 

302 self.start.add_edge_to(gn_decl) 

303 self.start = gn_decl 

304 

305 def attach_empty_assumption(self): 

306 assert not self.functional 

307 

308 # Attach new graph node advance start 

309 gn_decl = graph.Assumption(self.graph) 

310 self.start.add_edge_to(gn_decl) 

311 self.start = gn_decl 

312 

313 def analyze(self): 

314 try: 

315 self.checks_on_composite_type(self.n_ctyp) 

316 except Unsupported as exc: # pragma: no cover 

317 self.mh.warning(exc.location, exc.message) 

318 

319 def checks_on_composite_type(self, n_ctyp): 

320 assert isinstance(n_ctyp, Composite_Type) 

321 

322 # Create node for global declarations 

323 gn_locals = graph.Assumption(self.graph) 

324 self.start.add_edge_to(gn_locals) 

325 self.start = gn_locals 

326 self.preamble = gn_locals 

327 

328 # Create local variables 

329 for n_component in n_ctyp.all_components(): 

330 self.tr_component_decl(n_component, self.start) 

331 

332 # Create paths for checks in two phases: 

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

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

335 # self-contained and are analyzed first. 

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

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

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

339 # available. 

340 for phase in (False, True): 

341 for n_check in n_ctyp.iter_checks(): 

342 if n_check.uses_field_access != phase: 

343 continue 

344 current_start = self.start 

345 self.tr_check(n_check) 

346 

347 # Only fatal checks contribute to the total knowledge 

348 if n_check.severity != "fatal": 

349 self.start = current_start 

350 

351 # Emit debug graph 

352 if self.debug: # pragma: no cover 

353 subprocess.run( 

354 ["dot", "-Tpdf", "-o%s.pdf" % self.vc_name], 

355 input=self.graph.debug_render_dot(), 

356 check=True, 

357 encoding="UTF-8", 

358 ) 

359 

360 # Generate VCs 

361 self.vcg.generate() 

362 

363 # Solve VCs and provide feedback 

364 nok_feasibility_checks = [] 

365 ok_feasibility_checks = set() 

366 nok_validity_checks = set() 

367 

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

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

370 with open( 

371 self.vc_name + "_%04u.smt2" % vc_id, "w", encoding="UTF-8" 

372 ) as fd: 

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

374 

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

376 # again on a different path 

377 if vc["feedback"].expect_unsat and vc["feedback"] in nok_validity_checks: 

378 continue 

379 

380 solver = CVC5_Solver() 

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( 

393 vc["feedback"].node.location, 

394 message, 

395 vc["feedback"].kind, 

396 self.create_counterexample(status, values), 

397 ) 

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

399 else: 

400 if status == "unsat": 

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

402 else: 

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

404 

405 # This is a bit wonky, but this way we make sure the ordering is 

406 # consistent 

407 for feedback in nok_feasibility_checks: 

408 if feedback not in ok_feasibility_checks: 

409 self.mh.check(feedback.node.location, feedback.message, feedback.kind) 

410 ok_feasibility_checks.add(feedback) 

411 

412 def create_counterexample(self, status, values): 

413 rv = [ 

414 "example %s triggering error:" % self.n_ctyp.__class__.__name__.lower(), 

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

416 ] 

417 

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

419 id_value = self.tr_component_value_name(n_component) 

420 id_valid = self.tr_component_valid_name(n_component) 

421 if status == "unknown" and ( 421 ↛ 424line 421 didn't jump to line 424 because the condition on line 421 was never true

422 id_value not in values or id_valid not in values 

423 ): 

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

425 elif values.get(id_valid): 

426 rv.append( 

427 " %s = %s" 

428 % ( 

429 n_component.name, 

430 self.value_to_trlc(n_component.n_typ, values[id_value]), 

431 ) 

432 ) 

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 ↛ 518line 508 didn't jump to line 518 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" % ( 

513 n_typ.n_package.name, 

514 n_typ.name, 

515 instance_id, 

516 ) 

517 else: 

518 return "instance_%i" % instance_id 

519 

520 elif isinstance(n_typ, Tuple_Type): 

521 parts = [] 

522 for n_item in n_typ.iter_sequence(): 

523 if isinstance(n_item, Composite_Component): 

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

525 parts.pop() 

526 break 

527 parts.append( 

528 self.value_to_trlc(n_item.n_typ, value[n_item.name + ".value"]) 

529 ) 

530 

531 else: 

532 assert isinstance(n_item, Separator) 

533 sep_text = {"AT": "@", "COLON": ":", "SEMICOLON": ";"}.get( 

534 n_item.token.kind, n_item.token.value 

535 ) 

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( 

545 self.value_to_trlc(n_typ.element_type, item) for item in value 

546 ) 

547 

548 else: # pragma: no cover 

549 self.flag_unsupported(n_typ, "back-conversion from %s" % n_typ.name) 

550 

551 def tr_component_value_name(self, n_component): 

552 return ( 

553 n_component.member_of.fully_qualified_name() 

554 + "." 

555 + n_component.name 

556 + ".value" 

557 ) 

558 

559 def tr_component_valid_name(self, n_component): 

560 return ( 

561 n_component.member_of.fully_qualified_name() 

562 + "." 

563 + n_component.name 

564 + ".valid" 

565 ) 

566 

567 def emit_tuple_constraints(self, n_tuple, s_sym): 

568 assert isinstance(n_tuple, Tuple_Type) 

569 assert isinstance(s_sym, smt.Constant) 

570 

571 old_functional, self.functional = self.functional, True 

572 self.tuple_base[n_tuple] = s_sym 

573 

574 constraints = [] 

575 

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

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

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

579 # record. 

580 

581 for n_check in n_tuple.iter_checks(): 

582 if n_check.severity == "warning": 

583 continue 

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

585 # truth here. 

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

587 constraints.append(c_value) 

588 

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

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

591 

592 components = n_tuple.all_components() 

593 for i, component in enumerate(components): 

594 if component.optional: 

595 condition = smt.Boolean_Negation( 

596 smt.Record_Access(s_sym, component.name + ".valid") 

597 ) 

598 consequences = [ 

599 smt.Boolean_Negation(smt.Record_Access(s_sym, c.name + ".valid")) 

600 for c in components[i + 1 :] 

601 ] 

602 if len(consequences) == 0: 

603 break 

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

605 consequence = consequences[0] 

606 else: 

607 consequence = smt.Conjunction(*consequences) 

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

609 

610 del self.tuple_base[n_tuple] 

611 self.functional = old_functional 

612 

613 for cons in constraints: 

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

615 

616 def tr_component_decl(self, n_component, gn_locals): 

617 assert isinstance(n_component, Composite_Component) 

618 assert isinstance(gn_locals, graph.Assumption) 

619 

620 if isinstance(self.n_ctyp, Record_Type): 

621 frozen = self.n_ctyp.is_frozen(n_component) 

622 else: 

623 frozen = False 

624 

625 id_value = self.tr_component_value_name(n_component) 

626 s_sort = self.tr_type(n_component.n_typ) 

627 s_sym = smt.Constant(s_sort, id_value) 

628 if frozen: 

629 old_functional, self.functional = self.functional, True 

630 s_val, _ = self.tr_expression( 

631 self.n_ctyp.get_freezing_expression(n_component) 

632 ) 

633 self.functional = old_functional 

634 else: 

635 s_val = None 

636 s_decl = smt.Constant_Declaration( 

637 symbol=s_sym, 

638 value=s_val, 

639 comment="value for %s declared on %s" 

640 % (n_component.name, n_component.location.to_string()), 

641 relevant=True, 

642 ) 

643 gn_locals.add_statement(s_decl) 

644 self.constants[id_value] = s_sym 

645 

646 if isinstance(n_component.n_typ, Tuple_Type): 

647 self.emit_tuple_constraints(n_component.n_typ, s_sym) 

648 

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

650 # length 

651 if isinstance(n_component.n_typ, Array_Type): 

652 if n_component.n_typ.lower_bound > 0: 

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

654 gn_locals.add_statement( 

655 smt.Assertion( 

656 smt.Comparison(">=", smt.Sequence_Length(s_sym), s_lower) 

657 ) 

658 ) 

659 

660 if n_component.n_typ.upper_bound is not None: 

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

662 gn_locals.add_statement( 

663 smt.Assertion( 

664 smt.Comparison("<=", smt.Sequence_Length(s_sym), s_upper) 

665 ) 

666 ) 

667 

668 id_valid = self.tr_component_valid_name(n_component) 

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

670 s_val = ( 

671 None if n_component.optional and not frozen else smt.Boolean_Literal(True) 

672 ) 

673 s_decl = smt.Constant_Declaration(symbol=s_sym, value=s_val, relevant=True) 

674 gn_locals.add_statement(s_decl) 

675 self.constants[id_valid] = s_sym 

676 

677 def tr_type(self, n_type): 

678 assert isinstance(n_type, Type) 

679 

680 if isinstance(n_type, Builtin_Boolean): 

681 return smt.BUILTIN_BOOLEAN 

682 

683 elif isinstance(n_type, Builtin_Integer): 

684 return smt.BUILTIN_INTEGER 

685 

686 elif isinstance(n_type, Builtin_Decimal): 

687 return smt.BUILTIN_REAL 

688 

689 elif isinstance(n_type, Builtin_String): 

690 return smt.BUILTIN_STRING 

691 

692 elif isinstance(n_type, Enumeration_Type): 

693 if n_type not in self.enumerations: 

694 s_sort = smt.Enumeration(n_type.n_package.name + "." + n_type.name) 

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

696 s_sort.add_literal(n_lit.name) 

697 self.enumerations[n_type] = s_sort 

698 self.start.add_statement( 

699 smt.Enumeration_Declaration( 

700 s_sort, 

701 "enumeration %s from %s" 

702 % (n_type.name, n_type.location.to_string()), 

703 ) 

704 ) 

705 return self.enumerations[n_type] 

706 

707 elif isinstance(n_type, Tuple_Type): 

708 if n_type not in self.tuples: 

709 s_sort = smt.Record(n_type.n_package.name + "." + n_type.name) 

710 for n_component in n_type.all_components(): 

711 s_sort.add_component( 

712 n_component.name + ".value", self.tr_type(n_component.n_typ) 

713 ) 

714 if n_component.optional: 

715 s_sort.add_component( 

716 n_component.name + ".valid", smt.BUILTIN_BOOLEAN 

717 ) 

718 self.tuples[n_type] = s_sort 

719 self.start.add_statement( 

720 smt.Record_Declaration( 

721 s_sort, 

722 "tuple %s from %s" % (n_type.name, n_type.location.to_string()), 

723 ) 

724 ) 

725 

726 return self.tuples[n_type] 

727 

728 elif isinstance(n_type, Array_Type): 

729 if n_type not in self.arrays: 

730 s_element_sort = self.tr_type(n_type.element_type) 

731 s_sequence = smt.Sequence_Sort(s_element_sort) 

732 self.arrays[n_type] = s_sequence 

733 

734 return self.arrays[n_type] 

735 

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

737 # lobster-trace: LRM.union_type 

738 # Record and union references are modelled as a free 

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

740 # uninterpreted function. Some of these have special 

741 # meaning: 

742 # 0 - the null reference 

743 # 1 - the self reference 

744 # anything else - uninterpreted 

745 return smt.BUILTIN_INTEGER 

746 

747 else: # pragma: no cover 

748 self.flag_unsupported(n_type) 

749 

750 def tr_check(self, n_check): 

751 assert isinstance(n_check, Check) 

752 

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

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

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

756 # failed. 

757 if n_check.n_type is not self.n_ctyp: 

758 old_emit, self.emit_checks = self.emit_checks, False 

759 

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

761 self.attach_validity_check(valid, n_check.n_expr) 

762 self.attach_feasability_check(value, n_check.n_expr) 

763 self.attach_assumption(value) 

764 

765 if n_check.n_type is not self.n_ctyp: 

766 self.emit_checks = old_emit 

767 

768 def tr_expression(self, n_expr): 

769 value = None 

770 

771 if isinstance(n_expr, Name_Reference): 

772 return self.tr_name_reference(n_expr) 

773 

774 elif isinstance(n_expr, Unary_Expression): 

775 return self.tr_unary_expression(n_expr) 

776 

777 elif isinstance(n_expr, Binary_Expression): 

778 return self.tr_binary_expression(n_expr) 

779 

780 elif isinstance(n_expr, Range_Test): 

781 return self.tr_range_test(n_expr) 

782 

783 elif isinstance(n_expr, OneOf_Expression): 

784 return self.tr_oneof_test(n_expr) 

785 

786 elif isinstance(n_expr, Conditional_Expression): 

787 if self.functional: 

788 return self.tr_conditional_expression_functional(n_expr) 

789 else: 

790 return self.tr_conditional_expression(n_expr) 

791 

792 elif isinstance(n_expr, Null_Literal): 

793 return None, smt.Boolean_Literal(False) 

794 

795 elif isinstance(n_expr, Boolean_Literal): 

796 value = smt.Boolean_Literal(n_expr.value) 

797 

798 elif isinstance(n_expr, Integer_Literal): 

799 value = smt.Integer_Literal(n_expr.value) 

800 

801 elif isinstance(n_expr, Decimal_Literal): 

802 value = smt.Real_Literal(n_expr.value) 

803 

804 elif isinstance(n_expr, Enumeration_Literal): 

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

806 

807 elif isinstance(n_expr, String_Literal): 

808 value = smt.String_Literal(n_expr.value) 

809 

810 elif isinstance(n_expr, Quantified_Expression): 

811 return self.tr_quantified_expression(n_expr) 

812 

813 elif isinstance(n_expr, Field_Access_Expression): 

814 return self.tr_field_access_expression(n_expr) 

815 

816 else: # pragma: no cover 

817 self.flag_unsupported(n_expr) 

818 

819 return value, smt.Boolean_Literal(True) 

820 

821 def tr_name_reference(self, n_ref): 

822 assert isinstance(n_ref, Name_Reference) 

823 

824 if isinstance(n_ref.entity, Composite_Component): 

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

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

827 if n_ref.entity.optional: 

828 s_valid = smt.Record_Access(sym, n_ref.entity.name + ".valid") 

829 else: 

830 s_valid = smt.Boolean_Literal(True) 

831 s_value = smt.Record_Access(sym, n_ref.entity.name + ".value") 

832 return s_value, s_valid 

833 

834 else: 

835 id_value = self.tr_component_value_name(n_ref.entity) 

836 id_valid = self.tr_component_valid_name(n_ref.entity) 

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

838 

839 else: 

840 assert isinstance(n_ref.entity, Quantified_Variable) 

841 if n_ref.entity in self.qe_vars: 

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

843 else: 

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

845 

846 def tr_unary_expression(self, n_expr): 

847 assert isinstance(n_expr, Unary_Expression) 

848 

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

850 if not self.functional: 

851 self.attach_validity_check(operand_valid, n_expr.n_operand) 

852 

853 sym_value = None 

854 

855 if n_expr.operator == Unary_Operator.MINUS: 

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

857 sym_value = smt.Unary_Int_Arithmetic_Op("-", operand_value) 

858 else: 

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

860 sym_value = smt.Unary_Real_Arithmetic_Op("-", operand_value) 

861 

862 elif n_expr.operator == Unary_Operator.PLUS: 

863 sym_value = operand_value 

864 

865 elif n_expr.operator == Unary_Operator.LOGICAL_NOT: 

866 sym_value = smt.Boolean_Negation(operand_value) 

867 

868 elif n_expr.operator == Unary_Operator.ABSOLUTE_VALUE: 

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

870 sym_value = smt.Unary_Int_Arithmetic_Op("abs", operand_value) 

871 

872 else: 

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

874 sym_value = smt.Unary_Real_Arithmetic_Op("abs", operand_value) 

875 

876 elif n_expr.operator == Unary_Operator.STRING_LENGTH: 

877 sym_value = smt.String_Length(operand_value) 

878 

879 elif n_expr.operator == Unary_Operator.ARRAY_LENGTH: 

880 sym_value = smt.Sequence_Length(operand_value) 

881 

882 elif n_expr.operator == Unary_Operator.CONVERSION_TO_DECIMAL: 

883 sym_value = smt.Conversion_To_Real(operand_value) 

884 

885 elif n_expr.operator == Unary_Operator.CONVERSION_TO_INT: 

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

887 

888 else: 

889 self.mh.ice_loc( 

890 n_expr, "unexpected unary operator %s" % n_expr.operator.name 

891 ) 

892 

893 return self.create_return(n_expr, sym_value) 

894 

895 def tr_binary_expression(self, n_expr): 

896 assert isinstance(n_expr, Binary_Expression) 

897 

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

899 # deal with them first and then exit. 

900 if n_expr.operator in (Binary_Operator.COMP_EQ, Binary_Operator.COMP_NEQ): 

901 return self.tr_op_equality(n_expr) 

902 

903 elif n_expr.operator == Binary_Operator.LOGICAL_IMPLIES: 

904 return self.tr_op_implication(n_expr) 

905 

906 elif n_expr.operator == Binary_Operator.LOGICAL_AND: 

907 return self.tr_op_and(n_expr) 

908 

909 elif n_expr.operator == Binary_Operator.LOGICAL_OR: 

910 return self.tr_op_or(n_expr) 

911 

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

913 # obtain the values of both sides now. 

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

915 if not self.functional: 

916 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

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

918 if not self.functional: 

919 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

920 sym_value = None 

921 

922 if n_expr.operator == Binary_Operator.LOGICAL_XOR: 

923 sym_value = smt.Exclusive_Disjunction(lhs_value, rhs_value) 

924 

925 elif n_expr.operator in ( 

926 Binary_Operator.PLUS, 

927 Binary_Operator.MINUS, 

928 Binary_Operator.TIMES, 

929 Binary_Operator.DIVIDE, 

930 Binary_Operator.REMAINDER, 

931 ): 

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

933 assert n_expr.operator == Binary_Operator.PLUS 

934 sym_value = smt.String_Concatenation(lhs_value, rhs_value) 

935 

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

937 if n_expr.operator in ( 

938 Binary_Operator.DIVIDE, 

939 Binary_Operator.REMAINDER, 

940 ): 

941 self.attach_int_division_check(rhs_value, n_expr) 

942 

943 smt_op = { 

944 Binary_Operator.PLUS: "+", 

945 Binary_Operator.MINUS: "-", 

946 Binary_Operator.TIMES: "*", 

947 Binary_Operator.DIVIDE: "floor_div", 

948 Binary_Operator.REMAINDER: "ada_remainder", 

949 }[n_expr.operator] 

950 

951 sym_value = smt.Binary_Int_Arithmetic_Op(smt_op, lhs_value, rhs_value) 

952 

953 else: 

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

955 if n_expr.operator == Binary_Operator.DIVIDE: 

956 self.attach_real_division_check(rhs_value, n_expr) 

957 

958 smt_op = { 

959 Binary_Operator.PLUS: "+", 

960 Binary_Operator.MINUS: "-", 

961 Binary_Operator.TIMES: "*", 

962 Binary_Operator.DIVIDE: "/", 

963 }[n_expr.operator] 

964 

965 sym_value = smt.Binary_Real_Arithmetic_Op(smt_op, lhs_value, rhs_value) 

966 

967 elif n_expr.operator in ( 

968 Binary_Operator.COMP_LT, 

969 Binary_Operator.COMP_LEQ, 

970 Binary_Operator.COMP_GT, 

971 Binary_Operator.COMP_GEQ, 

972 ): 

973 smt_op = { 

974 Binary_Operator.COMP_LT: "<", 

975 Binary_Operator.COMP_LEQ: "<=", 

976 Binary_Operator.COMP_GT: ">", 

977 Binary_Operator.COMP_GEQ: ">=", 

978 }[n_expr.operator] 

979 

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

981 

982 elif n_expr.operator in ( 

983 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( 

996 smt_op[n_expr.operator], rhs_value, lhs_value 

997 ) 

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(), lhs_value, smt.String_Literal(rhs_evaluation) 

1005 ) 

1006 

1007 elif n_expr.operator == Binary_Operator.INDEX: 

1008 self.attach_index_check(lhs_value, rhs_value, n_expr) 

1009 sym_value = smt.Sequence_Index(lhs_value, rhs_value) 

1010 

1011 elif n_expr.operator == Binary_Operator.ARRAY_CONTAINS: 

1012 sym_value = smt.Sequence_Contains(rhs_value, lhs_value) 

1013 

1014 elif n_expr.operator == Binary_Operator.POWER: 

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

1016 # integer 

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

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

1019 

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

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

1022 sym_value = smt.Integer_Literal(1) 

1023 else: 

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

1025 sym_value = smt.Real_Literal(1) 

1026 

1027 else: 

1028 sym_value = lhs_value 

1029 for _ in range(1, static_value): 

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

1031 sym_value = smt.Binary_Int_Arithmetic_Op( 

1032 "*", sym_value, lhs_value 

1033 ) 

1034 else: 

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

1036 sym_value = smt.Binary_Real_Arithmetic_Op( 

1037 "*", sym_value, lhs_value 

1038 ) 

1039 

1040 else: # pragma: no cover 

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

1042 

1043 return self.create_return(n_expr, sym_value) 

1044 

1045 def tr_range_test(self, n_expr): 

1046 assert isinstance(n_expr, Range_Test) 

1047 

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

1049 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

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

1051 self.attach_validity_check(lower_valid, n_expr.n_lower) 

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

1053 self.attach_validity_check(upper_valid, n_expr.n_upper) 

1054 

1055 sym_value = smt.Conjunction( 

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

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

1058 ) 

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) for c in choices] 

1072 

1073 # pylint: disable=consider-using-enumerate 

1074 

1075 if len(choices) == 1: 

1076 result = choices[0] 

1077 elif len(choices) == 2: 

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

1079 else: 

1080 assert len(choices) >= 3 

1081 values = [] 

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

1083 sequence = [] 

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

1085 if other_id == choice_id: 

1086 sequence.append(choices[other_id]) 

1087 else: 

1088 sequence.append(negated_choices[other_id]) 

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

1090 result = smt.Disjunction(*values) 

1091 

1092 return self.create_return(n_expr, result) 

1093 

1094 def tr_conditional_expression_functional(self, n_expr): 

1095 assert isinstance(n_expr, Conditional_Expression) 

1096 

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

1098 for n_action in reversed(n_expr.actions): 

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

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

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

1102 

1103 return self.create_return(n_expr, s_result) 

1104 

1105 def tr_conditional_expression(self, n_expr): 

1106 assert isinstance(n_expr, Conditional_Expression) 

1107 assert not self.functional 

1108 

1109 gn_end = graph.Node(self.graph) 

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

1111 

1112 for n_action in n_expr.actions: 

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

1114 self.attach_validity_check(test_valid, n_action.n_cond) 

1115 current_start = self.start 

1116 

1117 # Create path where action is true 

1118 self.attach_assumption(test_value) 

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

1120 self.attach_validity_check(res_valid, n_action.n_expr) 

1121 self.attach_temp_declaration(n_action, sym_result, res_value) 

1122 self.start.add_edge_to(gn_end) 

1123 

1124 # Reset to test and proceed with the other actions 

1125 self.start = current_start 

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

1127 

1128 # Finally execute the else part 

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

1130 self.attach_validity_check(res_valid, n_expr.else_expr) 

1131 self.attach_temp_declaration(n_expr, sym_result, res_value) 

1132 self.start.add_edge_to(gn_end) 

1133 

1134 # And join 

1135 self.start = gn_end 

1136 return sym_result, smt.Boolean_Literal(True) 

1137 

1138 def tr_op_implication(self, n_expr): 

1139 assert isinstance(n_expr, Binary_Expression) 

1140 assert n_expr.operator == Binary_Operator.LOGICAL_IMPLIES 

1141 

1142 if self.functional: 

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

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

1145 return self.create_return(n_expr, smt.Implication(lhs_value, rhs_value)) 

1146 

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

1148 # Emit VC for validity 

1149 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1150 

1151 # Split into two paths. 

1152 current_start = self.start 

1153 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, self.new_temp_name()) 

1154 gn_end = graph.Node(self.graph) 

1155 

1156 ### 1: Implication is not valid 

1157 self.start = current_start 

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

1159 self.attach_temp_declaration(n_expr, sym_result, smt.Boolean_Literal(True)) 

1160 self.start.add_edge_to(gn_end) 

1161 

1162 ### 2: Implication is valid. 

1163 self.start = current_start 

1164 self.attach_assumption(lhs_value) 

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

1166 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1167 self.attach_temp_declaration(n_expr, sym_result, rhs_value) 

1168 self.start.add_edge_to(gn_end) 

1169 

1170 # Join paths 

1171 self.start = gn_end 

1172 

1173 return sym_result, smt.Boolean_Literal(True) 

1174 

1175 def tr_op_and(self, n_expr): 

1176 assert isinstance(n_expr, Binary_Expression) 

1177 assert n_expr.operator == Binary_Operator.LOGICAL_AND 

1178 

1179 if self.functional: 

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

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

1182 return self.create_return(n_expr, smt.Conjunction(lhs_value, rhs_value)) 

1183 

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

1185 # Emit VC for validity 

1186 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1187 

1188 # Split into two paths. 

1189 current_start = self.start 

1190 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, self.new_temp_name()) 

1191 gn_end = graph.Node(self.graph) 

1192 

1193 ### 1: LHS is not true 

1194 self.start = current_start 

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

1196 self.attach_temp_declaration(n_expr, sym_result, smt.Boolean_Literal(False)) 

1197 self.start.add_edge_to(gn_end) 

1198 

1199 ### 2: LHS is true 

1200 self.start = current_start 

1201 self.attach_assumption(lhs_value) 

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

1203 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1204 self.attach_temp_declaration(n_expr, sym_result, rhs_value) 

1205 self.start.add_edge_to(gn_end) 

1206 

1207 # Join paths 

1208 self.start = gn_end 

1209 

1210 return sym_result, smt.Boolean_Literal(True) 

1211 

1212 def tr_op_or(self, n_expr): 

1213 assert isinstance(n_expr, Binary_Expression) 

1214 assert n_expr.operator == Binary_Operator.LOGICAL_OR 

1215 

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

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

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

1219 return self.create_return(n_expr, smt.Disjunction(lhs_value, rhs_value)) 

1220 

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

1222 # Emit VC for validity 

1223 self.attach_validity_check(lhs_valid, n_expr.n_lhs) 

1224 

1225 # Split into two paths. 

1226 current_start = self.start 

1227 sym_result = smt.Constant(smt.BUILTIN_BOOLEAN, self.new_temp_name()) 

1228 gn_end = graph.Node(self.graph) 

1229 

1230 ### 1: LHS is true 

1231 self.start = current_start 

1232 self.attach_assumption(lhs_value) 

1233 self.attach_temp_declaration(n_expr, sym_result, smt.Boolean_Literal(True)) 

1234 self.start.add_edge_to(gn_end) 

1235 

1236 ### 2: LHS is not true 

1237 self.start = current_start 

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

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

1240 self.attach_validity_check(rhs_valid, n_expr.n_rhs) 

1241 self.attach_temp_declaration(n_expr, sym_result, rhs_value) 

1242 self.start.add_edge_to(gn_end) 

1243 

1244 # Join paths 

1245 self.start = gn_end 

1246 

1247 return sym_result, smt.Boolean_Literal(True) 

1248 

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

1250 assert isinstance(n_component, Composite_Component) 

1251 assert isinstance(lhs, smt.Expression) 

1252 assert isinstance(rhs, smt.Expression) 

1253 

1254 value_lhs = smt.Record_Access(lhs, n_component.name + ".value") 

1255 value_rhs = smt.Record_Access(rhs, n_component.name + ".value") 

1256 valid_equal = self.tr_core_equality(n_component.n_typ, value_lhs, value_rhs) 

1257 

1258 if not n_component.optional: 

1259 return valid_equal 

1260 

1261 valid_lhs = smt.Record_Access(lhs, n_component.name + ".valid") 

1262 valid_rhs = smt.Record_Access(rhs, n_component.name + ".valid") 

1263 

1264 return smt.Conjunction( 

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

1266 smt.Implication(valid_lhs, valid_equal), 

1267 ) 

1268 

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

1270 assert isinstance(n_typ, Type) 

1271 assert isinstance(lhs, smt.Expression) 

1272 assert isinstance(rhs, smt.Expression) 

1273 

1274 if isinstance(n_typ, Tuple_Type): 

1275 parts = [] 

1276 for n_component in n_typ.all_components(): 

1277 parts.append( 

1278 self.tr_core_equality_tuple_component(n_component, lhs, rhs) 

1279 ) 

1280 

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

1282 return smt.Boolean_Literal(True) 

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

1284 return parts[0] 

1285 else: 

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

1287 for part in parts[2:]: 

1288 result = smt.Conjunction(result, part) 

1289 return result 

1290 

1291 else: 

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

1293 

1294 def tr_op_equality(self, n_expr): 

1295 assert isinstance(n_expr, Binary_Expression) 

1296 assert n_expr.operator in (Binary_Operator.COMP_EQ, Binary_Operator.COMP_NEQ) 

1297 

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

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

1300 

1301 if lhs_value is None: 

1302 comp_typ = n_expr.n_rhs.typ 

1303 else: 

1304 comp_typ = n_expr.n_lhs.typ 

1305 

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

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

1308 result = self.tr_core_equality(comp_typ, lhs_value, rhs_value) 

1309 

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

1311 # This is null == null, so true 

1312 result = smt.Boolean_Literal(True) 

1313 

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

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

1316 result = smt.Boolean_Negation(rhs_valid) 

1317 

1318 elif rhs_value is None: 

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

1320 result = smt.Boolean_Negation(lhs_valid) 

1321 

1322 else: 

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

1324 result = smt.Conjunction( 

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

1326 smt.Implication( 

1327 lhs_valid, self.tr_core_equality(comp_typ, lhs_value, rhs_value) 

1328 ), 

1329 ) 

1330 

1331 if n_expr.operator == Binary_Operator.COMP_NEQ: 

1332 result = smt.Boolean_Negation(result) 

1333 

1334 return self.create_return(n_expr, result) 

1335 

1336 def tr_quantified_expression(self, n_expr): 

1337 assert isinstance(n_expr, Quantified_Expression) 

1338 

1339 # Nested quantifiers are not supported yet 

1340 if self.functional: # pragma: no cover 

1341 self.flag_unsupported(n_expr, "functional evaluation of quantifier") 

1342 

1343 # TRLC quantifier 

1344 # (forall x in arr_name => body) 

1345 # 

1346 # SMT quantifier 

1347 # (forall ((i Int)) 

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

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

1350 # 

1351 # There is an alternative which is: 

1352 # (forall ((element ElementSort)) 

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

1354 # (... element ...) 

1355 # 

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

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

1358 # value of some sequence member. 

1359 

1360 # Evaluate subject first and creat a null check 

1361 s_subject_value, s_subject_valid = self.tr_name_reference(n_expr.n_source) 

1362 self.attach_validity_check(s_subject_valid, n_expr.n_source) 

1363 

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

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

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

1367 # it's valid). 

1368 current_start = self.start 

1369 self.attach_empty_assumption() 

1370 src_typ = n_expr.n_source.typ 

1371 assert isinstance(src_typ, Array_Type) 

1372 s_qe_index = smt.Constant(smt.BUILTIN_INTEGER, self.new_temp_name()) 

1373 self.start.add_statement( 

1374 smt.Constant_Declaration( 

1375 symbol=s_qe_index, 

1376 comment=( 

1377 "quantifier elimination (index) for %s at %s" 

1378 % (n_expr.to_string(), n_expr.location.to_string()) 

1379 ), 

1380 ) 

1381 ) 

1382 self.start.add_statement( 

1383 smt.Assertion(smt.Comparison(">=", s_qe_index, smt.Integer_Literal(0))) 

1384 ) 

1385 self.start.add_statement( 

1386 smt.Assertion( 

1387 smt.Comparison("<", s_qe_index, smt.Sequence_Length(s_subject_value)) 

1388 ) 

1389 ) 

1390 s_qe_sym = smt.Constant( 

1391 self.tr_type(src_typ.element_type), self.new_temp_name() 

1392 ) 

1393 self.start.add_statement( 

1394 smt.Constant_Declaration( 

1395 symbol=s_qe_sym, 

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

1397 comment=( 

1398 "quantifier elimination (symbol) for %s at %s" 

1399 % (n_expr.to_string(), n_expr.location.to_string()) 

1400 ), 

1401 ) 

1402 ) 

1403 self.qe_vars[n_expr.n_var] = s_qe_sym 

1404 

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

1406 self.attach_validity_check(b_valid, n_expr.n_expr) 

1407 

1408 self.start = current_start 

1409 del self.qe_vars[n_expr.n_var] 

1410 

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

1412 # raise exception. Asserting the actual value of the 

1413 # quantifier is more awkward. 

1414 

1415 s_q_idx = smt.Bound_Variable(smt.BUILTIN_INTEGER, self.new_temp_name()) 

1416 s_q_sym = smt.Sequence_Index(s_subject_value, s_q_idx) 

1417 self.bound_vars[n_expr.n_var] = s_q_sym 

1418 

1419 temp, self.functional = self.functional, True 

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

1421 self.functional = temp 

1422 

1423 bounds_expr = smt.Conjunction( 

1424 smt.Comparison(">=", s_q_idx, smt.Integer_Literal(0)), 

1425 smt.Comparison("<", s_q_idx, smt.Sequence_Length(s_subject_value)), 

1426 ) 

1427 if n_expr.universal: 

1428 value = smt.Quantifier( 

1429 "forall", [s_q_idx], smt.Implication(bounds_expr, b_value) 

1430 ) 

1431 else: 

1432 value = smt.Quantifier( 

1433 "exists", [s_q_idx], smt.Conjunction(bounds_expr, b_value) 

1434 ) 

1435 

1436 return value, smt.Boolean_Literal(True) 

1437 

1438 def _ensure_record_deref(self, type_key, sort_name, uf_name, components): 

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

1440 for dereferencing integer-encoded references. 

1441 

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

1443 :param sort_name: name for the SMT Record sort 

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

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

1446 :returns: (record_sort, to_record_uf) 

1447 """ 

1448 if type_key in self.records: 

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

1450 

1451 record_sort = smt.Record(sort_name) 

1452 for field_name, field_sort, needs_valid in components: 

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

1454 if needs_valid: 

1455 record_sort.add_component(field_name + ".valid", smt.BUILTIN_BOOLEAN) 

1456 self.records[type_key] = record_sort 

1457 self.preamble.add_statement(smt.Record_Declaration(record_sort, sort_name)) 

1458 

1459 to_record_uf = smt.Function( 

1460 uf_name, record_sort, smt.Bound_Variable(smt.BUILTIN_INTEGER, "ref") 

1461 ) 

1462 self.preamble.add_statement(smt.Function_Declaration(to_record_uf)) 

1463 self.uf_records[type_key] = to_record_uf 

1464 

1465 return record_sort, to_record_uf 

1466 

1467 def tr_field_access_expression(self, n_expr): 

1468 assert isinstance(n_expr, Field_Access_Expression) 

1469 

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

1471 prefix_typ = n_expr.n_prefix.typ 

1472 if not self.functional: 

1473 self.attach_validity_check(prefix_valid, n_expr.n_prefix) 

1474 

1475 if isinstance(prefix_typ, Tuple_Type): 

1476 field_value = smt.Record_Access( 

1477 prefix_value, n_expr.n_field.name + ".value" 

1478 ) 

1479 if n_expr.n_field.optional: 

1480 field_valid = smt.Record_Access( 

1481 prefix_value, n_expr.n_field.name + ".valid" 

1482 ) 

1483 else: 

1484 field_valid = smt.Boolean_Literal(True) 

1485 

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

1487 # lobster-trace: LRM.Union_Type_Field_Access 

1488 # lobster-trace: LRM.Union_Type_Partial_Field_Access 

1489 # lobster-trace: LRM.Union_Type_Partial_Field_Null 

1490 # Both Record_Type and Union_Type are represented as 

1491 # integers. We create a record sort with accessible 

1492 # fields and a UF to dereference the integer. 

1493 if isinstance(prefix_typ, Record_Type): 

1494 components = [ 

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

1496 for c in prefix_typ.all_components() 

1497 ] 

1498 sort_name = "%s.%s" % (prefix_typ.n_package.name, prefix_typ.name) 

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

1500 else: 

1501 field_map = prefix_typ.get_field_map() 

1502 union_id = "_".join(t.fully_qualified_name() for t in prefix_typ.types) 

1503 components = [ 

1504 ( 

1505 name, 

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

1507 info["count"] != info["total"] or info["optional_in_any"], 

1508 ) 

1509 for name, info in field_map.items() 

1510 if info["n_typ"] is not None 

1511 ] 

1512 sort_name = "union." + union_id 

1513 uf_name = "access.union." + union_id 

1514 

1515 _, to_record_uf = self._ensure_record_deref( 

1516 sort_name, sort_name, uf_name, components 

1517 ) 

1518 dereference = smt.Function_Application(to_record_uf, prefix_value) 

1519 

1520 # Perform the field access on the dereferenced record 

1521 field_value = smt.Record_Access(dereference, n_expr.n_field.name + ".value") 

1522 if isinstance(prefix_typ, Union_Type): 

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

1524 has_valid = info["count"] != info["total"] or info["optional_in_any"] 

1525 else: 

1526 has_valid = n_expr.n_field.optional 

1527 

1528 if has_valid: 

1529 field_valid = smt.Record_Access( 

1530 dereference, n_expr.n_field.name + ".valid" 

1531 ) 

1532 else: 

1533 field_valid = smt.Boolean_Literal(True) 

1534 

1535 else: 

1536 self.mh.ice_loc( 

1537 n_expr.n_prefix.location, 

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

1539 % n_expr.n_prefix.typ.__class__.__name__, 

1540 ) 

1541 

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

1543 return field_value, field_valid