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