/* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file */ #include "sieve-common.h" #include "sieve-commands.h" #include "sieve-validator.h" #include "sieve-generator.h" #include "sieve-code.h" #include "sieve-binary.h" /* * Commands */ static bool cmd_if_validate (struct sieve_validator *valdtr, struct sieve_command *cmd); static bool cmd_elsif_validate (struct sieve_validator *valdtr, struct sieve_command *cmd); static bool cmd_if_validate_const (struct sieve_validator *valdtr, struct sieve_command *cmd, int *const_current, int const_next); static bool cmd_if_generate (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd); static bool cmd_else_generate (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd); /* If command * * Syntax: * if */ const struct sieve_command_def cmd_if = { .identifier = "if", .type = SCT_COMMAND, .positional_args = 0, .subtests = 1, .block_allowed = TRUE, .block_required = TRUE, .validate = cmd_if_validate, .validate_const = cmd_if_validate_const, .generate = cmd_if_generate }; /* ElsIf command * * Santax: * elsif */ const struct sieve_command_def cmd_elsif = { .identifier = "elsif", .type = SCT_COMMAND, .positional_args = 0, .subtests = 1, .block_allowed = TRUE, .block_required = TRUE, .validate = cmd_elsif_validate, .validate_const = cmd_if_validate_const, .generate = cmd_if_generate }; /* Else command * * Syntax: * else */ const struct sieve_command_def cmd_else = { .identifier = "else", .type = SCT_COMMAND, .positional_args = 0, .subtests = 0, .block_allowed = TRUE, .block_required = TRUE, .validate = cmd_elsif_validate, .validate_const = cmd_if_validate_const, .generate = cmd_else_generate }; /* * Context management */ struct cmd_if_context_data { struct cmd_if_context_data *previous; struct cmd_if_context_data *next; int const_condition; bool jump_generated; sieve_size_t exit_jump; }; static void cmd_if_initialize_context_data (struct sieve_command *cmd, struct cmd_if_context_data *previous) { struct cmd_if_context_data *cmd_data; /* Assign context */ cmd_data = p_new(sieve_command_pool(cmd), struct cmd_if_context_data, 1); cmd_data->exit_jump = 0; cmd_data->jump_generated = FALSE; /* Update linked list of contexts */ cmd_data->previous = previous; cmd_data->next = NULL; if ( previous != NULL ) previous->next = cmd_data; /* Check const status */ cmd_data->const_condition = -1; while ( previous != NULL ) { if ( previous->const_condition > 0 ) { cmd_data->const_condition = 0; break; } previous = previous->previous; } /* Assign to command context */ cmd->data = cmd_data; } /* * Validation */ static bool cmd_if_validate (struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd) { /* Start if-command structure */ cmd_if_initialize_context_data(cmd, NULL); return TRUE; } static bool cmd_elsif_validate (struct sieve_validator *valdtr, struct sieve_command *cmd) { struct sieve_command *prev; i_assert(cmd != NULL); prev = sieve_command_prev(cmd); /* Check valid command placement */ if ( prev == NULL || ( !sieve_command_is(prev, cmd_if) && !sieve_command_is(prev, cmd_elsif) ) ) { sieve_command_validate_error(valdtr, cmd, "the %s command must follow an if or elseif command", sieve_command_identifier(cmd)); return FALSE; } /* Previous command in this block is 'if' or 'elsif', so we can safely refer * to its context data */ cmd_if_initialize_context_data(cmd, prev->data); return TRUE; } static bool cmd_if_validate_const (struct sieve_validator *valdtr ATTR_UNUSED, struct sieve_command *cmd, int *const_current, int const_next) { struct cmd_if_context_data *cmd_data = (struct cmd_if_context_data *) cmd->data; if ( cmd_data != NULL ) { if ( cmd_data->const_condition == 0 ) { *const_current = cmd_data->const_condition; return FALSE; } cmd_data->const_condition = const_next; } *const_current = const_next; return ( const_next < 0 ); } /* * Code generation */ /* The if command does not generate specific IF-ELSIF-ELSE opcodes, but only uses * JMP instructions. This is why the implementation of the if command does not * include an opcode implementation. */ static void cmd_if_resolve_exit_jumps (struct sieve_binary_block *sblock, struct cmd_if_context_data *cmd_data) { struct cmd_if_context_data *if_ctx = cmd_data->previous; /* Iterate backwards through all if-command contexts and resolve the * exit jumps to the current code position. */ while ( if_ctx != NULL ) { if ( if_ctx->jump_generated ) sieve_binary_resolve_offset(sblock, if_ctx->exit_jump); if_ctx = if_ctx->previous; } } static bool cmd_if_generate (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd) { struct sieve_binary_block *sblock = cgenv->sblock; struct cmd_if_context_data *cmd_data = (struct cmd_if_context_data *) cmd->data; struct sieve_ast_node *test; struct sieve_jumplist jmplist; /* Generate test condition */ if ( cmd_data->const_condition < 0 ) { /* Prepare jumplist */ sieve_jumplist_init_temp(&jmplist, sblock); test = sieve_ast_test_first(cmd->ast_node); if ( !sieve_generate_test(cgenv, test, &jmplist, FALSE) ) return FALSE; } /* Case true { */ if ( cmd_data->const_condition != 0 ) { if ( !sieve_generate_block(cgenv, cmd->ast_node) ) return FALSE; } /* Are we the final command in this if-elsif-else structure? */ if ( cmd_data->next == NULL || cmd_data->const_condition == 1 ) { /* Yes, Resolve previous exit jumps to this point */ cmd_if_resolve_exit_jumps(sblock, cmd_data); } else if ( cmd_data->const_condition < 0 ) { /* No, generate jump to end of if-elsif-else structure (resolved later) * This of course is not necessary if the {} block contains a command * like stop at top level that unconditionally exits the block already * anyway. */ if ( !sieve_command_block_exits_unconditionally(cmd) ) { sieve_operation_emit(sblock, NULL, &sieve_jmp_operation); cmd_data->exit_jump = sieve_binary_emit_offset(sblock, 0); cmd_data->jump_generated = TRUE; } } if ( cmd_data->const_condition < 0 ) { /* Case false ... (subsequent elsif/else commands might generate more) */ sieve_jumplist_resolve(&jmplist); } return TRUE; } static bool cmd_else_generate (const struct sieve_codegen_env *cgenv, struct sieve_command *cmd) { struct cmd_if_context_data *cmd_data = (struct cmd_if_context_data *) cmd->data; /* Else { */ if ( cmd_data->const_condition != 0 ) { if ( !sieve_generate_block(cgenv, cmd->ast_node) ) return FALSE; /* } End: resolve all exit blocks */ cmd_if_resolve_exit_jumps(cgenv->sblock, cmd_data); } return TRUE; }