1 //===- ThreadSafetyTraverse.h -----------------------------------*- C++ -*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // This file defines a framework for doing generic traversals and rewriting
10 // operations over the Thread Safety TIL.
12 // UNDER CONSTRUCTION. USE AT YOUR OWN RISK.
14 //===----------------------------------------------------------------------===//
16 #ifndef LLVM_CLANG_ANALYSIS_ANALYSES_THREADSAFETYTRAVERSE_H
17 #define LLVM_CLANG_ANALYSIS_ANALYSES_THREADSAFETYTRAVERSE_H
19 #include "clang/AST/Decl.h"
20 #include "clang/Analysis/Analyses/ThreadSafetyTIL.h"
21 #include "clang/Analysis/Analyses/ThreadSafetyUtil.h"
22 #include "clang/Basic/LLVM.h"
23 #include "llvm/ADT/StringRef.h"
24 #include "llvm/Support/Casting.h"
29 namespace threadSafety {
32 // Defines an interface used to traverse SExprs. Traversals have been made as
33 // generic as possible, and are intended to handle any kind of pass over the
34 // AST, e.g. visitors, copying, non-destructive rewriting, destructive
35 // (in-place) rewriting, hashing, typing, etc.
37 // Traversals implement the functional notion of a "fold" operation on SExprs.
38 // Each SExpr class provides a traverse method, which does the following:
40 // // compute a result r_i for each subexpression e_i
41 // for (i = 1..n) r_i = v.traverse(e_i);
42 // // combine results into a result for e, where X is the class of e
43 // return v.reduceX(*e, r_1, .. r_n).
45 // A visitor can control the traversal by overriding the following methods:
47 // return v.traverseByCase(e), which returns v.traverseX(e)
48 // * v.traverseX(e): (X is the class of e)
49 // return e->traverse(v).
50 // * v.reduceX(*e, r_1, .. r_n):
51 // compute a result for a node of type X
53 // The reduceX methods control the kind of traversal (visitor, copy, etc.).
54 // They are defined in derived classes.
56 // Class R defines the basic interface types (R_SExpr).
57 template <class Self, class R>
60 Self *self() { return static_cast<Self *>(this); }
62 // Traverse an expression -- returning a result of type R_SExpr.
63 // Override this method to do something for every expression, regardless
64 // of which kind it is.
65 // E is a reference, so this can be use for in-place updates.
66 // The type T must be a subclass of SExpr.
68 typename R::R_SExpr traverse(T* &E, typename R::R_Ctx Ctx) {
69 return traverseSExpr(E, Ctx);
72 // Override this method to do something for every expression.
73 // Does not allow in-place updates.
74 typename R::R_SExpr traverseSExpr(SExpr *E, typename R::R_Ctx Ctx) {
75 return traverseByCase(E, Ctx);
78 // Helper method to call traverseX(e) on the appropriate type.
79 typename R::R_SExpr traverseByCase(SExpr *E, typename R::R_Ctx Ctx) {
80 switch (E->opcode()) {
81 #define TIL_OPCODE_DEF(X) \
83 return self()->traverse##X(cast<X>(E), Ctx);
84 #include "ThreadSafetyOps.def"
87 return self()->reduceNull();
90 // Traverse e, by static dispatch on the type "X" of e.
91 // Override these methods to do something for a particular kind of term.
92 #define TIL_OPCODE_DEF(X) \
93 typename R::R_SExpr traverse##X(X *e, typename R::R_Ctx Ctx) { \
94 return e->traverse(*self(), Ctx); \
96 #include "ThreadSafetyOps.def"
100 // Base class for simple reducers that don't much care about the context.
101 class SimpleReducerBase {
104 // Ordinary subexpressions.
107 // Declarations (e.g. function bodies).
110 // Expressions that require lazy evaluation.
117 // R_Ctx defines a "context" for the traversal, which encodes information
118 // about where a term appears. This can be used to encoding the
119 // "current continuation" for CPS transforms, or other information.
120 using R_Ctx = TraversalKind;
122 // Create context for an ordinary subexpression.
123 R_Ctx subExprCtx(R_Ctx Ctx) { return TRV_Normal; }
125 // Create context for a subexpression that occurs in a declaration position
126 // (e.g. function body).
127 R_Ctx declCtx(R_Ctx Ctx) { return TRV_Decl; }
129 // Create context for a subexpression that occurs in a position that
130 // should be reduced lazily. (e.g. code body).
131 R_Ctx lazyCtx(R_Ctx Ctx) { return TRV_Lazy; }
133 // Create context for a subexpression that occurs in a type position.
134 R_Ctx typeCtx(R_Ctx Ctx) { return TRV_Type; }
137 // Base class for traversals that rewrite an SExpr to another SExpr.
138 class CopyReducerBase : public SimpleReducerBase {
140 // R_SExpr is the result type for a traversal.
141 // A copy or non-destructive rewrite returns a newly allocated term.
142 using R_SExpr = SExpr *;
143 using R_BasicBlock = BasicBlock *;
145 // Container is a minimal interface used to store results when traversing
146 // SExprs of variable arity, such as Phi, Goto, and SCFG.
147 template <class T> class Container {
149 // Allocate a new container with a capacity for n elements.
150 Container(CopyReducerBase &S, unsigned N) : Elems(S.Arena, N) {}
152 // Push a new element onto the container.
153 void push_back(T E) { Elems.push_back(E); }
155 SimpleArray<T> Elems;
158 CopyReducerBase(MemRegionRef A) : Arena(A) {}
164 // Base class for visit traversals.
165 class VisitReducerBase : public SimpleReducerBase {
167 // A visitor returns a bool, representing success or failure.
168 using R_SExpr = bool;
169 using R_BasicBlock = bool;
171 // A visitor "container" is a single bool, which accumulates success.
172 template <class T> class Container {
176 Container(VisitReducerBase &S, unsigned N) {}
178 void push_back(bool E) { Success = Success && E; }
182 // Implements a traversal that visits each subexpression, and returns either
184 template <class Self>
185 class VisitReducer : public Traversal<Self, VisitReducerBase>,
186 public VisitReducerBase {
188 VisitReducer() = default;
191 R_SExpr reduceNull() { return true; }
192 R_SExpr reduceUndefined(Undefined &Orig) { return true; }
193 R_SExpr reduceWildcard(Wildcard &Orig) { return true; }
195 R_SExpr reduceLiteral(Literal &Orig) { return true; }
197 R_SExpr reduceLiteralT(LiteralT<T> &Orig) { return true; }
198 R_SExpr reduceLiteralPtr(Literal &Orig) { return true; }
200 R_SExpr reduceFunction(Function &Orig, Variable *Nvd, R_SExpr E0) {
204 R_SExpr reduceSFunction(SFunction &Orig, Variable *Nvd, R_SExpr E0) {
208 R_SExpr reduceCode(Code &Orig, R_SExpr E0, R_SExpr E1) {
212 R_SExpr reduceField(Field &Orig, R_SExpr E0, R_SExpr E1) {
216 R_SExpr reduceApply(Apply &Orig, R_SExpr E0, R_SExpr E1) {
220 R_SExpr reduceSApply(SApply &Orig, R_SExpr E0, R_SExpr E1) {
224 R_SExpr reduceProject(Project &Orig, R_SExpr E0) { return E0; }
225 R_SExpr reduceCall(Call &Orig, R_SExpr E0) { return E0; }
226 R_SExpr reduceAlloc(Alloc &Orig, R_SExpr E0) { return E0; }
227 R_SExpr reduceLoad(Load &Orig, R_SExpr E0) { return E0; }
228 R_SExpr reduceStore(Store &Orig, R_SExpr E0, R_SExpr E1) { return E0 && E1; }
230 R_SExpr reduceArrayIndex(Store &Orig, R_SExpr E0, R_SExpr E1) {
234 R_SExpr reduceArrayAdd(Store &Orig, R_SExpr E0, R_SExpr E1) {
238 R_SExpr reduceUnaryOp(UnaryOp &Orig, R_SExpr E0) { return E0; }
240 R_SExpr reduceBinaryOp(BinaryOp &Orig, R_SExpr E0, R_SExpr E1) {
244 R_SExpr reduceCast(Cast &Orig, R_SExpr E0) { return E0; }
246 R_SExpr reduceSCFG(SCFG &Orig, Container<BasicBlock *> Bbs) {
250 R_BasicBlock reduceBasicBlock(BasicBlock &Orig, Container<R_SExpr> &As,
251 Container<R_SExpr> &Is, R_SExpr T) {
252 return (As.Success && Is.Success && T);
255 R_SExpr reducePhi(Phi &Orig, Container<R_SExpr> &As) {
259 R_SExpr reduceGoto(Goto &Orig, BasicBlock *B) {
263 R_SExpr reduceBranch(Branch &O, R_SExpr C, BasicBlock *B0, BasicBlock *B1) {
267 R_SExpr reduceReturn(Return &O, R_SExpr E) {
271 R_SExpr reduceIdentifier(Identifier &Orig) {
275 R_SExpr reduceIfThenElse(IfThenElse &Orig, R_SExpr C, R_SExpr T, R_SExpr E) {
279 R_SExpr reduceLet(Let &Orig, Variable *Nvd, R_SExpr B) {
283 Variable *enterScope(Variable &Orig, R_SExpr E0) { return &Orig; }
284 void exitScope(const Variable &Orig) {}
285 void enterCFG(SCFG &Cfg) {}
286 void exitCFG(SCFG &Cfg) {}
287 void enterBasicBlock(BasicBlock &BB) {}
288 void exitBasicBlock(BasicBlock &BB) {}
290 Variable *reduceVariableRef(Variable *Ovd) { return Ovd; }
291 BasicBlock *reduceBasicBlockRef(BasicBlock *Obb) { return Obb; }
294 bool traverse(SExpr *E, TraversalKind K = TRV_Normal) {
295 Success = Success && this->traverseByCase(E);
299 static bool visit(SExpr *E) {
301 return Visitor.traverse(E, TRV_Normal);
308 // Basic class for comparison operations over expressions.
309 template <typename Self>
312 Self *self() { return reinterpret_cast<Self *>(this); }
315 bool compareByCase(const SExpr *E1, const SExpr* E2) {
316 switch (E1->opcode()) {
317 #define TIL_OPCODE_DEF(X) \
319 return cast<X>(E1)->compare(cast<X>(E2), *self());
320 #include "ThreadSafetyOps.def"
321 #undef TIL_OPCODE_DEF
327 class EqualsComparator : public Comparator<EqualsComparator> {
329 // Result type for the comparison, e.g. bool for simple equality,
330 // or int for lexigraphic comparison (-1, 0, 1). Must have one value which
334 CType trueResult() { return true; }
335 bool notTrue(CType ct) { return !ct; }
337 bool compareIntegers(unsigned i, unsigned j) { return i == j; }
338 bool compareStrings (StringRef s, StringRef r) { return s == r; }
339 bool comparePointers(const void* P, const void* Q) { return P == Q; }
341 bool compare(const SExpr *E1, const SExpr* E2) {
342 if (E1->opcode() != E2->opcode())
344 return compareByCase(E1, E2);
347 // TODO -- handle alpha-renaming of variables
348 void enterScope(const Variable *V1, const Variable *V2) {}
351 bool compareVariableRefs(const Variable *V1, const Variable *V2) {
355 static bool compareExprs(const SExpr *E1, const SExpr* E2) {
357 return Eq.compare(E1, E2);
361 class MatchComparator : public Comparator<MatchComparator> {
363 // Result type for the comparison, e.g. bool for simple equality,
364 // or int for lexigraphic comparison (-1, 0, 1). Must have one value which
368 CType trueResult() { return true; }
369 bool notTrue(CType ct) { return !ct; }
371 bool compareIntegers(unsigned i, unsigned j) { return i == j; }
372 bool compareStrings (StringRef s, StringRef r) { return s == r; }
373 bool comparePointers(const void *P, const void *Q) { return P == Q; }
375 bool compare(const SExpr *E1, const SExpr *E2) {
376 // Wildcards match anything.
377 if (E1->opcode() == COP_Wildcard || E2->opcode() == COP_Wildcard)
379 // otherwise normal equality.
380 if (E1->opcode() != E2->opcode())
382 return compareByCase(E1, E2);
385 // TODO -- handle alpha-renaming of variables
386 void enterScope(const Variable* V1, const Variable* V2) {}
389 bool compareVariableRefs(const Variable* V1, const Variable* V2) {
393 static bool compareExprs(const SExpr *E1, const SExpr* E2) {
394 MatchComparator Matcher;
395 return Matcher.compare(E1, E2);
399 // inline std::ostream& operator<<(std::ostream& SS, StringRef R) {
400 // return SS.write(R.data(), R.size());
403 // Pretty printer for TIL expressions
404 template <typename Self, typename StreamType>
405 class PrettyPrinter {
407 // Print out additional information.
410 // Omit redundant decls.
413 // Print exprs in C-like syntax.
417 PrettyPrinter(bool V = false, bool C = true, bool CS = true)
418 : Verbose(V), Cleanup(C), CStyle(CS) {}
420 static void print(const SExpr *E, StreamType &SS) {
422 printer.printSExpr(E, SS, Prec_MAX);
426 Self *self() { return reinterpret_cast<Self *>(this); }
428 void newline(StreamType &SS) {
432 // TODO: further distinguish between binary operations.
433 static const unsigned Prec_Atom = 0;
434 static const unsigned Prec_Postfix = 1;
435 static const unsigned Prec_Unary = 2;
436 static const unsigned Prec_Binary = 3;
437 static const unsigned Prec_Other = 4;
438 static const unsigned Prec_Decl = 5;
439 static const unsigned Prec_MAX = 6;
441 // Return the precedence of a given node, for use in pretty printing.
442 unsigned precedence(const SExpr *E) {
443 switch (E->opcode()) {
444 case COP_Future: return Prec_Atom;
445 case COP_Undefined: return Prec_Atom;
446 case COP_Wildcard: return Prec_Atom;
448 case COP_Literal: return Prec_Atom;
449 case COP_LiteralPtr: return Prec_Atom;
450 case COP_Variable: return Prec_Atom;
451 case COP_Function: return Prec_Decl;
452 case COP_SFunction: return Prec_Decl;
453 case COP_Code: return Prec_Decl;
454 case COP_Field: return Prec_Decl;
456 case COP_Apply: return Prec_Postfix;
457 case COP_SApply: return Prec_Postfix;
458 case COP_Project: return Prec_Postfix;
460 case COP_Call: return Prec_Postfix;
461 case COP_Alloc: return Prec_Other;
462 case COP_Load: return Prec_Postfix;
463 case COP_Store: return Prec_Other;
464 case COP_ArrayIndex: return Prec_Postfix;
465 case COP_ArrayAdd: return Prec_Postfix;
467 case COP_UnaryOp: return Prec_Unary;
468 case COP_BinaryOp: return Prec_Binary;
469 case COP_Cast: return Prec_Atom;
471 case COP_SCFG: return Prec_Decl;
472 case COP_BasicBlock: return Prec_MAX;
473 case COP_Phi: return Prec_Atom;
474 case COP_Goto: return Prec_Atom;
475 case COP_Branch: return Prec_Atom;
476 case COP_Return: return Prec_Other;
478 case COP_Identifier: return Prec_Atom;
479 case COP_IfThenElse: return Prec_Other;
480 case COP_Let: return Prec_Decl;
485 void printBlockLabel(StreamType & SS, const BasicBlock *BB, int index) {
498 void printSExpr(const SExpr *E, StreamType &SS, unsigned P, bool Sub=true) {
500 self()->printNull(SS);
503 if (Sub && E->block() && E->opcode() != COP_Variable) {
504 SS << "_x" << E->id();
507 if (self()->precedence(E) > P) {
508 // Wrap expr in () if necessary.
510 self()->printSExpr(E, SS, Prec_MAX);
515 switch (E->opcode()) {
516 #define TIL_OPCODE_DEF(X) \
518 self()->print##X(cast<X>(E), SS); \
520 #include "ThreadSafetyOps.def"
521 #undef TIL_OPCODE_DEF
525 void printNull(StreamType &SS) {
529 void printFuture(const Future *E, StreamType &SS) {
530 self()->printSExpr(E->maybeGetResult(), SS, Prec_Atom);
533 void printUndefined(const Undefined *E, StreamType &SS) {
537 void printWildcard(const Wildcard *E, StreamType &SS) {
542 void printLiteralT(const LiteralT<T> *E, StreamType &SS) {
546 void printLiteralT(const LiteralT<uint8_t> *E, StreamType &SS) {
547 SS << "'" << E->value() << "'";
550 void printLiteral(const Literal *E, StreamType &SS) {
551 if (E->clangExpr()) {
552 SS << getSourceLiteralString(E->clangExpr());
556 ValueType VT = E->valueType();
558 case ValueType::BT_Void:
561 case ValueType::BT_Bool:
562 if (E->as<bool>().value())
567 case ValueType::BT_Int:
569 case ValueType::ST_8:
571 printLiteralT(&E->as<int8_t>(), SS);
573 printLiteralT(&E->as<uint8_t>(), SS);
575 case ValueType::ST_16:
577 printLiteralT(&E->as<int16_t>(), SS);
579 printLiteralT(&E->as<uint16_t>(), SS);
581 case ValueType::ST_32:
583 printLiteralT(&E->as<int32_t>(), SS);
585 printLiteralT(&E->as<uint32_t>(), SS);
587 case ValueType::ST_64:
589 printLiteralT(&E->as<int64_t>(), SS);
591 printLiteralT(&E->as<uint64_t>(), SS);
597 case ValueType::BT_Float:
599 case ValueType::ST_32:
600 printLiteralT(&E->as<float>(), SS);
602 case ValueType::ST_64:
603 printLiteralT(&E->as<double>(), SS);
609 case ValueType::BT_String:
611 printLiteralT(&E->as<StringRef>(), SS);
614 case ValueType::BT_Pointer:
617 case ValueType::BT_ValueRef:
625 void printLiteralPtr(const LiteralPtr *E, StreamType &SS) {
626 SS << E->clangDecl()->getNameAsString();
629 void printVariable(const Variable *V, StreamType &SS, bool IsVarDecl=false) {
630 if (CStyle && V->kind() == Variable::VK_SFun)
633 SS << V->name() << V->id();
636 void printFunction(const Function *E, StreamType &SS, unsigned sugared = 0) {
639 SS << "\\("; // Lambda
642 SS << "("; // Slot declarations
645 SS << ", "; // Curried functions
648 self()->printVariable(E->variableDecl(), SS, true);
650 self()->printSExpr(E->variableDecl()->definition(), SS, Prec_MAX);
652 const SExpr *B = E->body();
653 if (B && B->opcode() == COP_Function)
654 self()->printFunction(cast<Function>(B), SS, 2);
657 self()->printSExpr(B, SS, Prec_Decl);
661 void printSFunction(const SFunction *E, StreamType &SS) {
663 self()->printVariable(E->variableDecl(), SS, true);
665 self()->printSExpr(E->body(), SS, Prec_Decl);
668 void printCode(const Code *E, StreamType &SS) {
670 self()->printSExpr(E->returnType(), SS, Prec_Decl-1);
672 self()->printSExpr(E->body(), SS, Prec_Decl);
675 void printField(const Field *E, StreamType &SS) {
677 self()->printSExpr(E->range(), SS, Prec_Decl-1);
679 self()->printSExpr(E->body(), SS, Prec_Decl);
682 void printApply(const Apply *E, StreamType &SS, bool sugared = false) {
683 const SExpr *F = E->fun();
684 if (F->opcode() == COP_Apply) {
685 printApply(cast<Apply>(F), SS, true);
688 self()->printSExpr(F, SS, Prec_Postfix);
691 self()->printSExpr(E->arg(), SS, Prec_MAX);
696 void printSApply(const SApply *E, StreamType &SS) {
697 self()->printSExpr(E->sfun(), SS, Prec_Postfix);
698 if (E->isDelegation()) {
700 self()->printSExpr(E->arg(), SS, Prec_MAX);
705 void printProject(const Project *E, StreamType &SS) {
708 if (const auto *SAP = dyn_cast<SApply>(E->record())) {
709 if (const auto *V = dyn_cast<Variable>(SAP->sfun())) {
710 if (!SAP->isDelegation() && V->kind() == Variable::VK_SFun) {
716 if (isa<Wildcard>(E->record())) {
717 // handle existentials
719 SS << E->clangDecl()->getQualifiedNameAsString();
723 self()->printSExpr(E->record(), SS, Prec_Postfix);
724 if (CStyle && E->isArrow())
731 void printCall(const Call *E, StreamType &SS) {
732 const SExpr *T = E->target();
733 if (T->opcode() == COP_Apply) {
734 self()->printApply(cast<Apply>(T), SS, true);
738 self()->printSExpr(T, SS, Prec_Postfix);
743 void printAlloc(const Alloc *E, StreamType &SS) {
745 self()->printSExpr(E->dataType(), SS, Prec_Other-1);
748 void printLoad(const Load *E, StreamType &SS) {
749 self()->printSExpr(E->pointer(), SS, Prec_Postfix);
754 void printStore(const Store *E, StreamType &SS) {
755 self()->printSExpr(E->destination(), SS, Prec_Other-1);
757 self()->printSExpr(E->source(), SS, Prec_Other-1);
760 void printArrayIndex(const ArrayIndex *E, StreamType &SS) {
761 self()->printSExpr(E->array(), SS, Prec_Postfix);
763 self()->printSExpr(E->index(), SS, Prec_MAX);
767 void printArrayAdd(const ArrayAdd *E, StreamType &SS) {
768 self()->printSExpr(E->array(), SS, Prec_Postfix);
770 self()->printSExpr(E->index(), SS, Prec_Atom);
773 void printUnaryOp(const UnaryOp *E, StreamType &SS) {
774 SS << getUnaryOpcodeString(E->unaryOpcode());
775 self()->printSExpr(E->expr(), SS, Prec_Unary);
778 void printBinaryOp(const BinaryOp *E, StreamType &SS) {
779 self()->printSExpr(E->expr0(), SS, Prec_Binary-1);
780 SS << " " << getBinaryOpcodeString(E->binaryOpcode()) << " ";
781 self()->printSExpr(E->expr1(), SS, Prec_Binary-1);
784 void printCast(const Cast *E, StreamType &SS) {
787 switch (E->castOpcode()) {
808 self()->printSExpr(E->expr(), SS, Prec_Unary);
812 self()->printSExpr(E->expr(), SS, Prec_Unary);
815 void printSCFG(const SCFG *E, StreamType &SS) {
817 for (const auto *BBI : *E)
818 printBasicBlock(BBI, SS);
823 void printBBInstr(const SExpr *E, StreamType &SS) {
825 if (E->opcode() == COP_Variable) {
826 const auto *V = cast<Variable>(E);
827 SS << "let " << V->name() << V->id() << " = ";
831 else if (E->opcode() != COP_Store) {
832 SS << "let _x" << E->id() << " = ";
834 self()->printSExpr(E, SS, Prec_MAX, Sub);
839 void printBasicBlock(const BasicBlock *E, StreamType &SS) {
840 SS << "BB_" << E->blockID() << ":";
842 SS << " BB_" << E->parent()->blockID();
845 for (const auto *A : E->arguments())
848 for (const auto *I : E->instructions())
851 const SExpr *T = E->terminator();
853 self()->printSExpr(T, SS, Prec_MAX, false);
860 void printPhi(const Phi *E, StreamType &SS) {
862 if (E->status() == Phi::PH_SingleVal)
863 self()->printSExpr(E->values()[0], SS, Prec_MAX);
866 for (const auto *V : E->values()) {
869 self()->printSExpr(V, SS, Prec_MAX);
875 void printGoto(const Goto *E, StreamType &SS) {
877 printBlockLabel(SS, E->targetBlock(), E->index());
880 void printBranch(const Branch *E, StreamType &SS) {
882 self()->printSExpr(E->condition(), SS, Prec_MAX);
884 printBlockLabel(SS, E->thenBlock(), -1);
886 printBlockLabel(SS, E->elseBlock(), -1);
889 void printReturn(const Return *E, StreamType &SS) {
891 self()->printSExpr(E->returnValue(), SS, Prec_Other);
894 void printIdentifier(const Identifier *E, StreamType &SS) {
898 void printIfThenElse(const IfThenElse *E, StreamType &SS) {
900 printSExpr(E->condition(), SS, Prec_Unary);
902 printSExpr(E->thenExpr(), SS, Prec_Unary);
904 printSExpr(E->elseExpr(), SS, Prec_Unary);
908 printSExpr(E->condition(), SS, Prec_MAX);
910 printSExpr(E->thenExpr(), SS, Prec_Other);
912 printSExpr(E->elseExpr(), SS, Prec_Other);
915 void printLet(const Let *E, StreamType &SS) {
917 printVariable(E->variableDecl(), SS, true);
919 printSExpr(E->variableDecl()->definition(), SS, Prec_Decl-1);
921 printSExpr(E->body(), SS, Prec_Decl-1);
925 class StdPrinter : public PrettyPrinter<StdPrinter, std::ostream> {};
928 } // namespace threadSafety
931 #endif // LLVM_CLANG_ANALYSIS_ANALYSES_THREADSAFETYTRAVERSE_H