blob: 8fd71174f0af8697bdc4ea4d749a51ff9dbdf734 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
|
/*
* Checks that variables are assigned and have allowed values
*
* Copyright © 2025 Samuel Lidén Borell <samuel@kodafritt.se>
*
* SPDX-License-Identifier: EUPL-1.2+ OR LGPL-2.1-or-later
*/
#include <assert.h>
#include "compiler.h"
static unsigned char scope_level = 0;
static unsigned char in_else[MAX_SCOPE_LEVEL+1] = { 0 };
static int block_terminates = 0;
static bool warned_about_unreachability = false;
void varstate_function_start(void)
{
assert(scope_level == 0);
block_terminates = 0;
warned_about_unreachability = false;
}
void varstate_enter_scope(void)
{
if (scope_level >= MAX_SCOPE_LEVEL) {
assert(scope_level == MAX_SCOPE_LEVEL);
error("Scope nesting deeper than 100 levels.");
}
scope_level++;
in_else[scope_level] = false;
}
static void check_maybe_unassigned(struct Var *var)
{
if (var->ifelse_assigned_level == scope_level) {
/* Previous block leaves it unassigned */
error("Variable is assigned in one case but not "
"the preceeding one");
var->assigned_at_level = OUT_OF_SCOPE;
}
}
void varstate_leave_scope(enum ScopeLeaveKind kind)
{
struct Var *var;
assert(scope_level > 0);
for (var = current_func->vardecls; var; var = var->next) {
if (var->declared_at_level == scope_level) {
var->declared_at_level = OUT_OF_SCOPE;
var->assigned_at_level = OUT_OF_SCOPE;
}
if (var->assigned_at_level == scope_level) {
/* TODO don't "merge" varstate if block_terminates */
var->assigned_at_level--;
switch (kind) {
case SLK_NORMAL:
check_maybe_unassigned(var);
break;
case SLK_NON_EXHAUSTIVE:
if (!var->is_modifiable) {
error("Final variable (non-`!`) is might be left in "
"uninitialized state here");
}
check_maybe_unassigned(var);
var->ifelse_assigned_level = scope_level;
break;
case SLK_LOOP:
if (!var->is_modifiable) {
error("Loop modifies a final (non-`!`) variable");
}
var->ifelse_assigned_level = scope_level;
break;
default: unreachable();
}
}
}
scope_level--;
if (block_terminates) {
block_terminates--;
}
}
void varstate_else(void)
{
struct Var *var;
for (var = current_func->vardecls; var; var = var->next) {
if (var->declared_at_level == scope_level) {
var->declared_at_level = OUT_OF_SCOPE;
var->assigned_at_level = OUT_OF_SCOPE;
}
if (var->assigned_at_level == scope_level) {
/* TODO don't "merge" varstate if block_terminates */
check_maybe_unassigned(var);
if (var->assigned_at_level != OUT_OF_SCOPE) {
var->ifelse_assigned_level = scope_level;
}
}
}
in_else[scope_level] = true;
if (block_terminates) {
block_terminates--;
}
}
static void terminate_block(void)
{
block_terminates++;
warned_about_unreachability = false;
}
void varstate_return(void)
{
terminate_block();
}
void varstate_break(void)
{
terminate_block();
/* TODO this one needs to find the containing loop */
}
void varstate_continue(void)
{
terminate_block();
/* TODO this one needs to find the containing loop */
}
void varstate_mark_declared(struct Var *var)
{
assert(var->declared_at_level == NOT_YET_DECLARED);
var->declared_at_level = scope_level;
}
/* TODO there are already checks for this inside the parser. Check if
that covers all cases, and if so, replace this one with an assert(). */
static void require_in_scope(const struct Var *var)
{
if (var->declared_at_level > scope_level) {
assert(var->declared_at_level == NOT_YET_DECLARED ||
var->declared_at_level == OUT_OF_SCOPE);
error(var->declared_at_level == NOT_YET_DECLARED ?
"Variable hasn't been declared yet" :
"Variable has gone out of scope here");
}
}
static bool in_any_else_upto(int upto_level)
{
int i;
assert(upto_level >= 0 && upto_level <= MAX_SCOPE_LEVEL);
for (i = scope_level; i > upto_level; i--) {
if (in_else[i]) {
return true;
}
}
return false;
}
void varstate_mark_assigned(struct Var *var)
{
require_in_scope(var);
if (!var->is_modifiable &&
var->assigned_at_level != NOT_YET_ASSIGNED &&
var->ifelse_assigned_level != scope_level) {
error(var->ifelse_assigned_level == NOT_IFELSE_ASSIGNED ?
"Variable is not marked with `!` and may not be modified" :
"Nested conditional in else is too complex "
"for variable initialization check");
}
/* FIXME this will not work with e.g.
if ...
return
else
x = 1
end
- this can be solved through a special varstate_noreturn();
function that sets a flag (for that scope, i.e. in a array),
that in turn causes varstate_leave_scope/varstate_else
to work differently (basically it should skip most/all
checks and updates)
- note that after return/continue/break, the
block MUST end (or a warning should be reported).
- also add checks that all code paths of a function
return a value (if the function returns a value)
*/
/* This might be a bit too strict (it disallows declarations far out
from the usage scope, if the usage is nested in if/else) */
if (in_any_else_upto(var->declared_at_level) &&
var->assigned_at_level >= scope_level &&
var->ifelse_assigned_level != scope_level) {
error("Variable is not assigned in preceeding block");
}
if (var->ifelse_assigned_level != scope_level) {
if (var->assigned_at_level > scope_level) {
var->assigned_at_level = scope_level;
}
} else {
if (var->assigned_at_level < scope_level) {
var->assigned_at_level = scope_level;
}
}
var->ifelse_assigned_level = NOT_IFELSE_ASSIGNED;
}
void varstate_require_assigned(const struct Var *var)
{
if (var->is_funcparam || var->is_giveme) {
return;
}
require_in_scope(var);
if (var->assigned_at_level > scope_level ||
var->ifelse_assigned_level == scope_level) {
error(var->assigned_at_level == NOT_YET_ASSIGNED ?
"Variable hasn't been assigned yet" :
"Variable might not be assigned here");
}
}
void varstate_warn_if_unreachable(void)
{
if (block_terminates != 0 && !warned_about_unreachability) {
warning("Unreachable code");
warned_about_unreachability = true;
}
}
/* TODO none-ness checking */
|