Coverage Report

Created: 2026-05-30 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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