/src/pigeonhole/src/lib-sieve/cmd-if.c
Line | Count | Source |
1 | | /* Copyright (c) Pigeonhole authors, see top-level COPYING file */ |
2 | | |
3 | | #include "sieve-common.h" |
4 | | #include "sieve-commands.h" |
5 | | #include "sieve-validator.h" |
6 | | #include "sieve-generator.h" |
7 | | #include "sieve-code.h" |
8 | | #include "sieve-binary.h" |
9 | | |
10 | | /* |
11 | | * Commands |
12 | | */ |
13 | | |
14 | | static bool |
15 | | cmd_if_validate(struct sieve_validator *valdtr, struct sieve_command *cmd); |
16 | | static bool |
17 | | cmd_elsif_validate(struct sieve_validator *valdtr, struct sieve_command *cmd); |
18 | | static bool |
19 | | cmd_if_validate_const(struct sieve_validator *valdtr, struct sieve_command *cmd, |
20 | | int *const_current, int const_next); |
21 | | static bool |
22 | | cmd_if_generate(const struct sieve_codegen_env *cgenv, |
23 | | struct sieve_command *cmd); |
24 | | static bool |
25 | | cmd_else_generate(const struct sieve_codegen_env *cgenv, |
26 | | struct sieve_command *cmd); |
27 | | |
28 | | /* If command |
29 | | * |
30 | | * Syntax: |
31 | | * if <test1: test> <block1: block> |
32 | | */ |
33 | | |
34 | | const struct sieve_command_def cmd_if = { |
35 | | .identifier = "if", |
36 | | .type = SCT_COMMAND, |
37 | | .positional_args = 0, |
38 | | .subtests = 1, |
39 | | .block_allowed = TRUE, |
40 | | .block_required = TRUE, |
41 | | .validate = cmd_if_validate, |
42 | | .validate_const = cmd_if_validate_const, |
43 | | .generate = cmd_if_generate |
44 | | }; |
45 | | |
46 | | /* ElsIf command |
47 | | * |
48 | | * Santax: |
49 | | * elsif <test2: test> <block2: block> |
50 | | */ |
51 | | |
52 | | const struct sieve_command_def cmd_elsif = { |
53 | | .identifier = "elsif", |
54 | | .type = SCT_COMMAND, |
55 | | .positional_args = 0, |
56 | | .subtests = 1, |
57 | | .block_allowed = TRUE, |
58 | | .block_required = TRUE, |
59 | | .validate = cmd_elsif_validate, |
60 | | .validate_const = cmd_if_validate_const, |
61 | | .generate = cmd_if_generate |
62 | | }; |
63 | | |
64 | | /* Else command |
65 | | * |
66 | | * Syntax: |
67 | | * else <block> |
68 | | */ |
69 | | |
70 | | const struct sieve_command_def cmd_else = { |
71 | | .identifier = "else", |
72 | | .type = SCT_COMMAND, |
73 | | .positional_args = 0, |
74 | | .subtests = 0, |
75 | | .block_allowed = TRUE, |
76 | | .block_required = TRUE, |
77 | | .validate = cmd_elsif_validate, |
78 | | .validate_const = cmd_if_validate_const, |
79 | | .generate = cmd_else_generate |
80 | | }; |
81 | | |
82 | | /* |
83 | | * Context management |
84 | | */ |
85 | | |
86 | | struct cmd_if_context_data { |
87 | | struct cmd_if_context_data *previous; |
88 | | struct cmd_if_context_data *next; |
89 | | |
90 | | int const_condition; |
91 | | |
92 | | bool jump_generated; |
93 | | sieve_size_t exit_jump; |
94 | | }; |
95 | | |
96 | | static void |
97 | | cmd_if_initialize_context_data(struct sieve_command *cmd, |
98 | | struct cmd_if_context_data *previous) |
99 | 0 | { |
100 | 0 | struct cmd_if_context_data *cmd_data; |
101 | | |
102 | | /* Assign context */ |
103 | 0 | cmd_data = p_new(sieve_command_pool(cmd), |
104 | 0 | struct cmd_if_context_data, 1); |
105 | 0 | cmd_data->exit_jump = 0; |
106 | 0 | cmd_data->jump_generated = FALSE; |
107 | | |
108 | | /* Update linked list of contexts */ |
109 | 0 | cmd_data->previous = previous; |
110 | 0 | cmd_data->next = NULL; |
111 | 0 | if (previous != NULL) |
112 | 0 | previous->next = cmd_data; |
113 | | |
114 | | /* Check const status */ |
115 | 0 | cmd_data->const_condition = -1; |
116 | 0 | while (previous != NULL) { |
117 | 0 | if (previous->const_condition > 0) { |
118 | 0 | cmd_data->const_condition = 0; |
119 | 0 | break; |
120 | 0 | } |
121 | 0 | previous = previous->previous; |
122 | 0 | } |
123 | | |
124 | | /* Assign to command context */ |
125 | 0 | cmd->data = cmd_data; |
126 | 0 | } |
127 | | |
128 | | /* |
129 | | * Validation |
130 | | */ |
131 | | |
132 | | static bool |
133 | | cmd_if_validate(struct sieve_validator *valdtr ATTR_UNUSED, |
134 | | struct sieve_command *cmd) |
135 | 0 | { |
136 | | /* Start if-command structure */ |
137 | 0 | cmd_if_initialize_context_data(cmd, NULL); |
138 | |
|
139 | 0 | return TRUE; |
140 | 0 | } |
141 | | |
142 | | static bool |
143 | | cmd_elsif_validate(struct sieve_validator *valdtr, struct sieve_command *cmd) |
144 | 0 | { |
145 | 0 | struct sieve_command *prev; |
146 | |
|
147 | 0 | i_assert(cmd != NULL); |
148 | 0 | prev = sieve_command_prev(cmd); |
149 | | |
150 | | /* Check valid command placement */ |
151 | 0 | if (prev == NULL || |
152 | 0 | (!sieve_command_is(prev, cmd_if) && |
153 | 0 | !sieve_command_is(prev, cmd_elsif))) |
154 | 0 | { |
155 | 0 | sieve_command_validate_error( |
156 | 0 | valdtr, cmd, |
157 | 0 | "the %s command must follow an if or elseif command", |
158 | 0 | sieve_command_identifier(cmd)); |
159 | 0 | return FALSE; |
160 | 0 | } |
161 | | |
162 | | /* Previous command in this block is 'if' or 'elsif', so we can safely |
163 | | refer to its context data. */ |
164 | 0 | cmd_if_initialize_context_data(cmd, prev->data); |
165 | |
|
166 | 0 | return TRUE; |
167 | 0 | } |
168 | | |
169 | | static bool |
170 | | cmd_if_validate_const(struct sieve_validator *valdtr ATTR_UNUSED, |
171 | | struct sieve_command *cmd, int *const_current, |
172 | | int const_next) |
173 | 0 | { |
174 | 0 | struct cmd_if_context_data *cmd_data = |
175 | 0 | (struct cmd_if_context_data *)cmd->data; |
176 | |
|
177 | 0 | if (cmd_data != NULL) { |
178 | 0 | if (cmd_data->const_condition == 0) { |
179 | 0 | *const_current = cmd_data->const_condition; |
180 | 0 | return FALSE; |
181 | 0 | } |
182 | | |
183 | 0 | cmd_data->const_condition = const_next; |
184 | 0 | } |
185 | | |
186 | 0 | *const_current = const_next; |
187 | |
|
188 | 0 | return (const_next < 0); |
189 | 0 | } |
190 | | |
191 | | /* |
192 | | * Code generation |
193 | | */ |
194 | | |
195 | | /* The if command does not generate specific IF-ELSIF-ELSE opcodes, but only |
196 | | uses JMP instructions. This is why the implementation of the if command does |
197 | | not include an opcode implementation. |
198 | | */ |
199 | | |
200 | | static void |
201 | | cmd_if_resolve_exit_jumps(struct sieve_binary_block *sblock, |
202 | | struct cmd_if_context_data *cmd_data) |
203 | 0 | { |
204 | 0 | struct cmd_if_context_data *if_ctx = cmd_data->previous; |
205 | | |
206 | | /* Iterate backwards through all if-command contexts and resolve the |
207 | | exit jumps to the current code position. */ |
208 | 0 | while (if_ctx != NULL) { |
209 | 0 | if (if_ctx->jump_generated) |
210 | 0 | sieve_binary_resolve_offset(sblock, if_ctx->exit_jump); |
211 | 0 | if_ctx = if_ctx->previous; |
212 | 0 | } |
213 | 0 | } |
214 | | |
215 | | static bool |
216 | | cmd_if_generate(const struct sieve_codegen_env *cgenv, |
217 | | struct sieve_command *cmd) |
218 | 0 | { |
219 | 0 | struct sieve_binary_block *sblock = cgenv->sblock; |
220 | 0 | struct cmd_if_context_data *cmd_data = |
221 | 0 | (struct cmd_if_context_data *)cmd->data; |
222 | 0 | struct sieve_ast_node *test; |
223 | 0 | struct sieve_jumplist jmplist; |
224 | | |
225 | | /* Generate test condition */ |
226 | 0 | if (cmd_data->const_condition < 0) { |
227 | | /* Prepare jumplist */ |
228 | 0 | sieve_jumplist_init_temp(&jmplist, sblock); |
229 | |
|
230 | 0 | test = sieve_ast_test_first(cmd->ast_node); |
231 | 0 | if (!sieve_generate_test(cgenv, test, &jmplist, FALSE)) |
232 | 0 | return FALSE; |
233 | 0 | } |
234 | | |
235 | | /* Case true { */ |
236 | 0 | if (cmd_data->const_condition != 0) { |
237 | 0 | if (!sieve_generate_block(cgenv, cmd->ast_node)) |
238 | 0 | return FALSE; |
239 | 0 | } |
240 | | |
241 | | /* Are we the final command in this if-elsif-else structure? */ |
242 | 0 | if (cmd_data->next == NULL || cmd_data->const_condition == 1) { |
243 | | /* Yes, Resolve previous exit jumps to this point */ |
244 | 0 | cmd_if_resolve_exit_jumps(sblock, cmd_data); |
245 | 0 | } else if (cmd_data->const_condition < 0) { |
246 | | /* No, generate jump to end of if-elsif-else structure (resolved |
247 | | later). This of course is not necessary if the {} block |
248 | | contains a command like stop at top level that |
249 | | unconditionally exits the block already anyway. |
250 | | */ |
251 | 0 | if (!sieve_command_block_exits_unconditionally(cmd)) { |
252 | 0 | sieve_operation_emit(sblock, NULL, |
253 | 0 | &sieve_jmp_operation); |
254 | 0 | cmd_data->exit_jump = |
255 | 0 | sieve_binary_emit_offset(sblock, 0); |
256 | 0 | cmd_data->jump_generated = TRUE; |
257 | 0 | } |
258 | 0 | } |
259 | |
|
260 | 0 | if (cmd_data->const_condition < 0) { |
261 | | /* Case false ... |
262 | | (subsequent elsif/else commands might generate more) */ |
263 | 0 | sieve_jumplist_resolve(&jmplist); |
264 | 0 | } |
265 | |
|
266 | 0 | return TRUE; |
267 | 0 | } |
268 | | |
269 | | static bool |
270 | | cmd_else_generate(const struct sieve_codegen_env *cgenv, |
271 | | struct sieve_command *cmd) |
272 | 0 | { |
273 | 0 | struct cmd_if_context_data *cmd_data = |
274 | 0 | (struct cmd_if_context_data *)cmd->data; |
275 | | |
276 | | /* Else { */ |
277 | 0 | if (cmd_data->const_condition != 0) { |
278 | 0 | if (!sieve_generate_block(cgenv, cmd->ast_node)) |
279 | 0 | return FALSE; |
280 | | |
281 | | /* } End: resolve all exit blocks */ |
282 | 0 | cmd_if_resolve_exit_jumps(cgenv->sblock, cmd_data); |
283 | 0 | } |
284 | | |
285 | 0 | return TRUE; |
286 | 0 | } |