Joos1W Compiler Framework
All Classes Functions Typedefs Pages
ExprStaticChecker.cc
1 #include "semantic/ExprStaticChecker.h"
2 
3 #include "ast/AST.h"
4 #include "ast/Decl.h"
5 #include "ast/DeclContext.h"
6 #include "ast/ExprNode.h"
7 #include "ast/Type.h"
8 #include "diagnostics/Diagnostics.h"
9 #include "utils/Utils.h"
10 
11 namespace semantic {
12 
13 using ETy = ExprStaticCheckerData;
14 using ESC = ExprStaticChecker;
15 namespace ex = ast::exprnode;
16 using namespace diagnostics;
17 
18 // FIXME(kevin): We seem to use this in a few places, maybe move to header?
19 static bool isDeclStatic(ast::Decl const* decl) {
20  if(auto* field = dyn_cast<ast::FieldDecl>(decl)) {
21  return field->modifiers().isStatic();
22  } else if(auto* method = dyn_cast<ast::MethodDecl>(decl)) {
23  return method->modifiers().isStatic();
24  }
25  return false;
26 }
27 
28 void ESC::Evaluate(ast::Expr* expr, ExprStaticCheckerState state) {
29  this->state = state;
30  loc_ = expr->location();
31  ETy single = this->EvaluateList(expr->list());
32  // Handle the case of a single member access
33  checkInstanceVar(single);
34 }
35 
36 ETy ESC::mapValue(ExprValue& node) const {
37  // If node is "this", reject immediately
38  if(dyn_cast<ex::ThisNode>(node) && state.isStaticContext) {
39  throw diag.ReportError(node.location())
40  << "cannot use 'this' in a static context";
41  }
42 
43  // 1. This node is a pure type node
44  // 2. This node is a value node, with type and decl
45  // 3. This node is a literal, with type but no decl
46  assert(dyn_cast<ex::MethodName>(node) || node.isTypeResolved());
47  if(dyn_cast<ex::LiteralNode>(node)) {
48  return ETy{nullptr, node.type(), true, false};
49  } else if(dyn_cast<ex::TypeNode>(node)) {
50  return ETy{nullptr, node.type(), false, false};
51  } else {
52  bool isParentClass = dyn_cast<ast::ClassDecl>(node.decl()->parent());
53  bool isStaticModifier = isDeclStatic(node.decl());
54  bool isInstanceVar = isParentClass && !isStaticModifier;
55  return ETy{node.decl(), node.type(), true, isInstanceVar};
56  }
57 }
58 
59 ETy ESC::evalBinaryOp(BinaryOp& op, ETy lhs, ETy rhs) const {
60  assert(op.resultType());
61  if(op.opType() == ast::exprnode::BinaryOp::OpType::Assignment) {
62  // Then we can safely ignore LHS field access
63  checkInstanceVar(lhs, false);
64  // If the LHS is a field, it must not be final
65  if(auto field = dyn_cast_or_null<ast::FieldDecl>(lhs.decl)) {
66  if(field->modifiers().isFinal()) {
67  throw diag.ReportError(loc_) << "cannot assign to a final field";
68  }
69  } else if (auto var = dyn_cast_or_null<ast::VarDecl>(lhs.decl)) {
70  op.setVarAssigned(var);
71  }
72  } else {
73  checkInstanceVar(lhs, true);
74  }
75  checkInstanceVar(rhs);
76  return ETy{nullptr, op.resultType(), true, false};
77 }
78 
79 ETy ESC::evalUnaryOp(UnaryOp& op, ETy val) const {
80  assert(op.resultType());
81  checkInstanceVar(val);
82  return ETy{nullptr, op.resultType(), true, false};
83 }
84 
85 ETy ESC::evalMemberAccess(DotOp& op, ETy lhs, ETy field) const {
86  assert(op.resultType());
87  // LHS may never be a type (it might not have a decl for ex. temporaries)
88  assert(lhs.isValue);
89  // RHS must be a field and have a resolved declaration
90  assert(field.isValue && field.decl);
91  // Only check if LHS is an instance variable
92  checkInstanceVar(lhs);
93  isAccessible(lhs, field);
94  // The field must not be static because this is "instance . field"
95  if(isDeclStatic(field.decl)) {
96  throw diag.ReportError(loc_)
97  << "cannot access a static field through an instance variable"
98  << argLocation(1) << "field is static";
99  }
100  // See NOTE in ETy on why instance var is false here!
101  return ETy{field.decl, op.resultType(), true, false};
102 }
103 
104 ETy ESC::evalMethodCall(MethodOp& op, ETy method, const op_array& args) const {
105  // Method must be value and have a resolved declaration
106  assert(method.isValue && method.decl);
107  // Accessing instance method in a static context
108  checkInstanceVar(method);
109  // And also all the arguments
110  for(auto arg : args) {
111  assert(arg.isValue);
112  checkInstanceVar(arg);
113  }
114  // We don't assert op.resultType() because it can be null i.e., void
115  return ETy{nullptr, op.resultType(), true, false};
116 }
117 
118 ETy ESC::evalNewObject(NewOp& op, ETy ty, const op_array& args) const {
119  assert(!ty.isValue && ty.type);
120  for(auto arg : args) {
121  assert(arg.isValue);
122  checkInstanceVar(arg);
123  }
124  assert(op.resultType());
125  return ETy{nullptr, op.resultType(), true, false};
126 }
127 
128 ETy ESC::evalNewArray(NewArrayOp& op, ETy type, ETy size) const {
129  assert(!type.isValue && type.type);
130  assert(size.isValue);
131  checkInstanceVar(size);
132  assert(op.resultType());
133  return ETy{nullptr, op.resultType(), true, false};
134 }
135 
136 ETy ESC::evalArrayAccess(ArrayAccessOp& op, ETy arr, ETy idx) const {
137  assert(arr.isValue);
138  assert(idx.isValue);
139  checkInstanceVar(arr);
140  checkInstanceVar(idx);
141  assert(op.resultType());
142  return ETy{nullptr, op.resultType(), true, false};
143 }
144 
145 ETy ESC::evalCast(CastOp& op, ETy type, ETy obj) const {
146  assert(!type.isValue && type.type);
147  assert(obj.isValue);
148  assert(op.resultType());
149  checkInstanceVar(obj);
150  return ETy{nullptr, op.resultType(), true, false};
151 }
152 
153 void ESC::isAccessible(ETy lhs, ETy var) const {
154  if(!var.isInstanceVar) return;
155  auto lhsVar = dyn_cast_or_null<ast::VarDecl>(lhs.decl);
156  if(!lhsVar) return;
157  auto lhsRef = dyn_cast<ast::ReferenceType>(lhsVar->type());
158  if(!lhsRef) return;
159  auto lhsClass = dyn_cast<ast::ClassDecl>(lhsRef->decl());
160  if(!lhsClass) return;
161  if(auto method = dyn_cast<ast::MethodDecl>(var.decl)) {
162  if(method->modifiers().isProtected()) {
163  auto cur_cu = dyn_cast<ast::CompilationUnit>(state.currentClass->parent());
164  auto other_cu = dyn_cast<ast::CompilationUnit>(lhsClass->parent());
165  if(!HC.isSuperClass(state.currentClass, lhsClass)
166  && cur_cu->getPackageName() != other_cu->getPackageName()) {
167  throw diag.ReportError(loc_)
168  << "cannot access protected method" << argLocation(0)
169  << "instance of " << lhsClass->name() << argLocation(1)
170  << "protected member";
171  }
172  }
173  } else if(auto field = dyn_cast<ast::FieldDecl>(var.decl)) {
174  if(field->modifiers().isProtected()) {
175  auto cur_cu = dyn_cast<ast::CompilationUnit>(state.currentClass->parent());
176  auto other_cu = dyn_cast<ast::CompilationUnit>(lhsClass->parent());
177  if(!HC.isSuperClass(state.currentClass, lhsClass)
178  && cur_cu->getPackageName() != other_cu->getPackageName()) {
179  throw diag.ReportError(loc_)
180  << "cannot access protected field" << argLocation(0)
181  << "instance of " << lhsClass->name() << argLocation(1)
182  << "protected member";
183  }
184  }
185  }
186  return;
187 }
188 
189 void ESC::checkInstanceVar(ETy var, bool checkInitOrder) const {
190  if(!var.isInstanceVar) return;
191  // Instance variable must not be accessed in a static context
192  if(state.isStaticContext) {
193  throw diag.ReportError(loc_)
194  << "cannot access or invoke instance members in a static context";
195  }
196  // Instance variable accessed in a field initializer must satisfy
197  // lexical order
198  if(state.isInstFieldInitializer && checkInitOrder) {
199  auto fieldDecl = cast<ast::FieldDecl>(var.decl);
200  if(!state.fieldScope->canView(fieldDecl->scope())) {
201  throw diag.ReportError(loc_) << "cannot access instance members in "
202  "initializer before they are defined";
203  }
204  }
205 }
206 
207 } // namespace semantic