Coverage Report

Created: 2026-08-31 07:00

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) 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
}