Merge branch 'event-bug-test' of git://github.com/JanKanis/fish-shell into JanKanis-event-bug-test

This commit is contained in:
ridiculousfish 2012-12-22 12:20:41 -08:00
commit 8a66ba6c35
13 changed files with 217 additions and 156 deletions

View file

@ -1009,15 +1009,15 @@ static int builtin_emit(parser_t &parser, wchar_t **argv)
}
for (; woptind < argc; woptind++)
{
event_fire_generic(argv[woptind]);
if(!argv[woptind]) {
append_format(stderr_buffer, L"%ls: expected event name\n", argv[0]);
return STATUS_BUILTIN_ERROR;
}
wchar_t *eventname = argv[woptind];
wcstring_list_t args(argv + woptind + 1, argv + argc);
event_fire_generic(eventname, &args);
return STATUS_BUILTIN_OK;
}
@ -1099,7 +1099,7 @@ static void functions_def(const wcstring &name, wcstring &out)
search.function_name = name;
std::vector<event_t *> ev;
event_get(&search, &ev);
event_get(search, &ev);
out.append(L"function ");

View file

@ -1,11 +1,11 @@
\section emit emit - Emit a generic event
\subsection block-synopsis Synopsis
<tt>emit EVENT_NAME</tt>
<tt>emit EVENT_NAME [ARGUMENTS...]</tt>
\subsection emit-description Description
The emit builtin fires a generic fish event. Such events can be caught by special functions called event handlers.
The emit builtin fires a generic fish event. Such events can be caught by special functions called event handlers. The arguments are passed to the event handlers as function arguments.
\subsection emit-example Example
@ -13,7 +13,8 @@ The following code first defines an event handler for the generic
event named 'test_event', and then emits an event of that type.
<pre>function event_test --on-event test_event
echo event test!!!
echo event test: $argv
end
emit test_event</pre>
emit test_event something
</pre>

24
env.cpp
View file

@ -415,12 +415,10 @@ static void universal_callback(fish_message_type_t type,
mark_changed_exported();
event_t ev = event_t::variable_event(name);
ev.arguments.reset(new wcstring_list_t());
ev.arguments->push_back(L"VARIABLE");
ev.arguments->push_back(str);
ev.arguments->push_back(name);
ev.arguments.push_back(L"VARIABLE");
ev.arguments.push_back(str);
ev.arguments.push_back(name);
event_fire(&ev);
ev.arguments.reset(NULL);
}
if (name)
@ -961,15 +959,13 @@ int env_set(const wcstring &key, const wchar_t *val, int var_mode)
if (!is_universal)
{
event_t ev = event_t::variable_event(key);
ev.arguments.reset(new wcstring_list_t);
ev.arguments->push_back(L"VARIABLE");
ev.arguments->push_back(L"SET");
ev.arguments->push_back(key);
ev.arguments.push_back(L"VARIABLE");
ev.arguments.push_back(L"SET");
ev.arguments.push_back(key);
// debug( 1, L"env_set: fire events on variable %ls", key );
event_fire(&ev);
// debug( 1, L"env_set: return from event firing" );
ev.arguments.reset(NULL);
}
react_to_variable_change(key);
@ -1048,14 +1044,12 @@ int env_remove(const wcstring &key, int var_mode)
if (try_remove(first_node, key.c_str(), var_mode))
{
event_t ev = event_t::variable_event(key);
ev.arguments.reset(new wcstring_list_t);
ev.arguments->push_back(L"VARIABLE");
ev.arguments->push_back(L"ERASE");
ev.arguments->push_back(key);
ev.arguments.push_back(L"VARIABLE");
ev.arguments.push_back(L"ERASE");
ev.arguments.push_back(key);
event_fire(&ev);
ev.arguments.reset(NULL);
erased = 1;
}
}

250
event.cpp
View file

@ -25,6 +25,7 @@
#include "event.h"
#include "signal.h"
/**
Number of signals that can be queued before an overflow occurs
*/
@ -86,137 +87,120 @@ static event_list_t blocked;
they must name the same function.
*/
static int event_match(const event_t *classv, const event_t *instance)
static int event_match(const event_t &classv, const event_t &instance)
{
/* If the function names are both non-empty and different, then it's not a match */
if (! classv->function_name.empty() &&
! instance->function_name.empty() &&
classv->function_name != instance->function_name)
if (! classv.function_name.empty() &&
! instance.function_name.empty() &&
classv.function_name != instance.function_name)
{
return 0;
}
if (classv->type == EVENT_ANY)
if (classv.type == EVENT_ANY)
return 1;
if (classv->type != instance->type)
if (classv.type != instance.type)
return 0;
switch (classv->type)
switch (classv.type)
{
case EVENT_SIGNAL:
if (classv->param1.signal == EVENT_ANY_SIGNAL)
if (classv.param1.signal == EVENT_ANY_SIGNAL)
return 1;
return classv->param1.signal == instance->param1.signal;
return classv.param1.signal == instance.param1.signal;
case EVENT_VARIABLE:
return instance->str_param1 == classv->str_param1;
return instance.str_param1 == classv.str_param1;
case EVENT_EXIT:
if (classv->param1.pid == EVENT_ANY_PID)
if (classv.param1.pid == EVENT_ANY_PID)
return 1;
return classv->param1.pid == instance->param1.pid;
return classv.param1.pid == instance.param1.pid;
case EVENT_JOB_ID:
return classv->param1.job_id == instance->param1.job_id;
return classv.param1.job_id == instance.param1.job_id;
case EVENT_GENERIC:
return instance->str_param1 == classv->str_param1;
return instance.str_param1 == classv.str_param1;
}
/**
This should never be reached
*/
debug(0, "Warning: Unreachable code reached in event_match in event.cpp\n");
return 0;
}
/**
Create an identical copy of an event. Use deep copying, i.e. make
duplicates of any strings used as well.
*/
static event_t *event_copy(const event_t *event, int copy_arguments)
{
event_t *e = new event_t(*event);
e->arguments.reset(new wcstring_list_t);
if (copy_arguments && event->arguments.get() != NULL)
{
*(e->arguments) = *(event->arguments);
}
return e;
}
/**
Test if specified event is blocked
*/
static int event_is_blocked(event_t *e)
static int event_is_blocked(const event_t &e)
{
block_t *block;
parser_t &parser = parser_t::principal_parser();
for (block = parser.current_block; block; block = block->outer)
{
if (event_block_list_blocks_type(block->event_blocks, e->type))
if (event_block_list_blocks_type(block->event_blocks, e.type))
return true;
}
return event_block_list_blocks_type(parser.global_event_blocks, e->type);
return event_block_list_blocks_type(parser.global_event_blocks, e.type);
}
wcstring event_get_desc(const event_t *e)
wcstring event_get_desc(const event_t &e)
{
CHECK(e, 0);
wcstring result;
switch (e->type)
switch (e.type)
{
case EVENT_SIGNAL:
result = format_string(_(L"signal handler for %ls (%ls)"), sig2wcs(e->param1.signal), signal_get_desc(e->param1.signal));
result = format_string(_(L"signal handler for %ls (%ls)"), sig2wcs(e.param1.signal), signal_get_desc(e.param1.signal));
break;
case EVENT_VARIABLE:
result = format_string(_(L"handler for variable '%ls'"), e->str_param1.c_str());
result = format_string(_(L"handler for variable '%ls'"), e.str_param1.c_str());
break;
case EVENT_EXIT:
if (e->param1.pid > 0)
if (e.param1.pid > 0)
{
result = format_string(_(L"exit handler for process %d"), e->param1.pid);
result = format_string(_(L"exit handler for process %d"), e.param1.pid);
}
else
{
job_t *j = job_get_from_pid(-e->param1.pid);
job_t *j = job_get_from_pid(-e.param1.pid);
if (j)
result = format_string(_(L"exit handler for job %d, '%ls'"), j->job_id, j->command_wcstr());
else
result = format_string(_(L"exit handler for job with process group %d"), -e->param1.pid);
result = format_string(_(L"exit handler for job with process group %d"), -e.param1.pid);
}
break;
case EVENT_JOB_ID:
{
job_t *j = job_get(e->param1.job_id);
job_t *j = job_get(e.param1.job_id);
if (j)
result = format_string(_(L"exit handler for job %d, '%ls'"), j->job_id, j->command_wcstr());
else
result = format_string(_(L"exit handler for job with job id %d"), e->param1.job_id);
result = format_string(_(L"exit handler for job with job id %d"), e.param1.job_id);
break;
}
case EVENT_GENERIC:
result = format_string(_(L"handler for generic event '%ls'"), e->str_param1.c_str());
result = format_string(_(L"handler for generic event '%ls'"), e.str_param1.c_str());
break;
default:
result = format_string(_(L"Unknown event type"));
result = format_string(_(L"Unknown event type '0x%x'"), e.type);
break;
}
@ -237,13 +221,82 @@ static void show_all_handlers(void)
}
#endif
void event_add_handler(const event_t *event)
/*
Give a more condensed description of \c event compared to \c event_get_desc.
It includes what function will fire if the \c event is an event handler.
*/
static wcstring event_desc_compact(const event_t &event) {
wcstring res;
wchar_t const *temp;
int sig;
switch(event.type) {
case EVENT_ANY:
res = L"EVENT_ANY";
break;
case EVENT_VARIABLE:
if(event.str_param1.c_str()) {
res = format_string(L"EVENT_VARIABLE($%ls)", event.str_param1.c_str());
} else {
res = L"EVENT_VARIABLE([any])";
}
break;
case EVENT_SIGNAL:
sig = event.param1.signal;
if(sig == EVENT_ANY_SIGNAL) {
temp = L"[all signals]";
} else if(sig == 0) {
temp = L"not set";
} else {
temp = sig2wcs(sig);
}
res = format_string(L"EVENT_SIGNAL(%ls)", temp);
break;
case EVENT_EXIT:
if(event.param1.pid == EVENT_ANY_PID) {
res = wcstring(L"EVENT_EXIT([all child processes])");
} else if (event.param1.pid > 0) {
res = format_string(L"EVENT_EXIT(pid %d)", event.param1.pid);
} else {
job_t *j = job_get_from_pid(-event.param1.pid);
if (j)
res = format_string(L"EVENT_EXIT(jobid %d: \"%ls\")", j->job_id, j->command_wcstr());
else
res = format_string(L"EVENT_EXIT(pgid %d)", -event.param1.pid);
}
break;
case EVENT_JOB_ID:
{
job_t *j = job_get(event.param1.job_id);
if (j)
res = format_string(L"EVENT_JOB_ID(job %d: \"%ls\")", j->job_id, j->command_wcstr());
else
res = format_string(L"EVENT_JOB_ID(jobid %d)", event.param1.job_id);
break;
}
case EVENT_GENERIC:
res = format_string(L"EVENT_GENERIC(%ls)", event.str_param1.c_str());
break;
default:
res = format_string(L"unknown/illegal event(%x)", event.type);
}
if(event.function_name.size()) {
return format_string(L"%ls: \"%ls\"", res.c_str(), event.function_name.c_str());
} else {
return res;
}
}
void event_add_handler(const event_t &event)
{
event_t *e;
CHECK(event,);
if(debug_level >= 3) {
wcstring desc = event_desc_compact(event);
debug(3, "register: %ls\n", desc.c_str());
}
e = event_copy(event, 0);
e = new event_t(event);
if (e->type == EVENT_SIGNAL)
{
@ -256,13 +309,16 @@ void event_add_handler(const event_t *event)
signal_unblock();
}
void event_remove(event_t *criterion)
void event_remove(const event_t &criterion)
{
size_t i;
event_list_t new_list;
CHECK(criterion,);
if(debug_level >= 3) {
wcstring desc = event_desc_compact(criterion);
debug(3, "unregister: %ls\n", desc.c_str());
}
/*
Because of concurrency issues (env_remove could remove an event
@ -279,7 +335,7 @@ void event_remove(event_t *criterion)
for (i=0; i<events.size(); i++)
{
event_t *n = events.at(i);
if (event_match(criterion, n))
if (event_match(criterion, *n))
{
killme.push_back(n);
@ -291,7 +347,7 @@ void event_remove(event_t *criterion)
if (n->type == EVENT_SIGNAL)
{
event_t e = event_t::signal_event(n->param1.signal);
if (event_get(&e, 0) == 1)
if (event_get(e, 0) == 1)
{
signal_handle(e.param1.signal, 0);
}
@ -307,7 +363,7 @@ void event_remove(event_t *criterion)
signal_unblock();
}
int event_get(event_t *criterion, std::vector<event_t *> *out)
int event_get(const event_t &criterion, std::vector<event_t *> *out)
{
size_t i;
int found = 0;
@ -315,12 +371,10 @@ int event_get(event_t *criterion, std::vector<event_t *> *out)
if (events.empty())
return 0;
CHECK(criterion, 0);
for (i=0; i<events.size(); i++)
{
event_t *n = events.at(i);
if (event_match(criterion, n))
if (event_match(criterion, *n))
{
found++;
if (out)
@ -364,9 +418,9 @@ static void event_free_kills()
/**
Test if the specified event is waiting to be killed
*/
static int event_is_killed(event_t *e)
static int event_is_killed(const event_t &e)
{
return std::find(killme.begin(), killme.end(), e) != killme.end();
return std::find(killme.begin(), killme.end(), &e) != killme.end();
}
/**
@ -375,16 +429,18 @@ static int event_is_killed(event_t *e)
optimize the 'no matches' path. This means that nothing is
allocated/initialized unless needed.
*/
static void event_fire_internal(const event_t *event)
static void event_fire_internal(const event_t &event)
{
size_t i, j;
event_list_t fire;
/*
First we free all events that have been removed
First we free all events that have been removed, but only if this
invocation of event_fire_internal is not a recursive call.
*/
event_free_kills();
if(is_event <= 1)
event_free_kills();
if (events.empty())
return;
@ -403,7 +459,7 @@ static void event_fire_internal(const event_t *event)
/*
Check if this event is a match
*/
if (event_match(criterion, event))
if (event_match(*criterion, event))
{
fire.push_back(criterion);
}
@ -427,7 +483,7 @@ static void event_fire_internal(const event_t *event)
/*
Check if this event has been removed, if so, dont fire it
*/
if (event_is_killed(criterion))
if (event_is_killed(*criterion))
continue;
/*
@ -435,17 +491,17 @@ static void event_fire_internal(const event_t *event)
*/
wcstring buffer = criterion->function_name;
if (event->arguments.get())
if (! event.arguments.empty())
{
for (j=0; j< event->arguments->size(); j++)
for (j=0; j < event.arguments.size(); j++)
{
wcstring arg_esc = escape_string(event->arguments->at(j), 1);
wcstring arg_esc = escape_string(event.arguments.at(j), 1);
buffer += L" ";
buffer += arg_esc;
}
}
// debug( 1, L"Event handler fires command '%ls'", buffer.c_str() );
// debug( 1, L"Event handler fires command '%ls'", buffer.c_str() );
/*
Event handlers are not part of the main flow of code, so
@ -466,7 +522,8 @@ static void event_fire_internal(const event_t *event)
/*
Free killed events
*/
event_free_kills();
if(is_event <= 1)
event_free_kills();
}
@ -492,13 +549,13 @@ static void event_fire_delayed()
for (i=0; i<blocked.size(); i++)
{
event_t *e = blocked.at(i);
if (event_is_blocked(e))
if (event_is_blocked(*e))
{
new_blocked.push_back(e);
new_blocked.push_back(new event_t(*e));
}
else
{
event_fire_internal(e);
event_fire_internal(*e);
event_free(e);
}
}
@ -520,7 +577,7 @@ static void event_fire_delayed()
Set up
*/
event_t e = event_t::signal_event(0);
e.arguments.reset(new wcstring_list_t(1)); //one element
e.arguments.resize(1);
lst = &sig_list[1-active_list];
if (lst->overflow)
@ -534,19 +591,17 @@ static void event_fire_delayed()
for (int i=0; i < lst->count; i++)
{
e.param1.signal = lst->signal[i];
e.arguments->at(0) = sig2wcs(e.param1.signal);
if (event_is_blocked(&e))
e.arguments.at(0) = sig2wcs(e.param1.signal);
if (event_is_blocked(e))
{
blocked.push_back(event_copy(&e, 1));
blocked.push_back(new event_t(e));
}
else
{
event_fire_internal(&e);
event_fire_internal(e);
}
}
e.arguments.reset(NULL);
}
}
@ -583,13 +638,13 @@ void event_fire(event_t *event)
if (event)
{
if (event_is_blocked(event))
if (event_is_blocked(*event))
{
blocked.push_back(event_copy(event, 1));
blocked.push_back(new event_t(*event));
}
else
{
event_fire_internal(event);
event_fire_internal(*event);
}
}
is_event--;
@ -617,26 +672,15 @@ void event_free(event_t *e)
delete e;
}
void event_fire_generic_internal(const wchar_t *name, ...)
void event_fire_generic(const wchar_t *name, wcstring_list_t *args)
{
va_list va;
wchar_t *arg;
CHECK(name,);
event_t ev(EVENT_GENERIC);
ev.str_param1 = name;
ev.arguments.reset(new wcstring_list_t);
va_start(va, name);
while ((arg=va_arg(va, wchar_t *))!= 0)
{
ev.arguments->push_back(arg);
}
va_end(va);
if (args)
ev.arguments = *args;
event_fire(&ev);
ev.arguments.reset(NULL);
}
event_t event_t::signal_event(int sig)
@ -659,14 +703,4 @@ event_t event_t::generic_event(const wcstring &str)
event.str_param1 = str;
return event;
}
event_t::event_t(const event_t &x) :
type(x.type),
param1(x.param1),
str_param1(x.str_param1),
function_name(x.function_name)
{
const wcstring_list_t *ptr = x.arguments.get();
if (ptr)
arguments.reset(new wcstring_list_t(*ptr));
}

19
event.h
View file

@ -84,12 +84,13 @@ struct event_t
event_fire. In all other situations, the value of this variable
is ignored.
*/
std::auto_ptr<wcstring_list_t> arguments;
wcstring_list_t arguments;
event_t(int t) : type(t), param1(), str_param1(), function_name(), arguments() { }
/** Copy constructor */
event_t(const event_t &x);
/** default copy constructor */
//event_t(const event_t &x);
static event_t signal_event(int sig);
static event_t variable_event(const wcstring &str);
@ -101,14 +102,14 @@ struct event_t
May not be called by a signal handler, since it may allocate new memory.
*/
void event_add_handler(const event_t *event);
void event_add_handler(const event_t &event);
/**
Remove all events matching the specified criterion.
May not be called by a signal handler, since it may free allocated memory.
*/
void event_remove(event_t *event);
void event_remove(const event_t &event);
/**
Return all events which match the specified event class
@ -121,7 +122,7 @@ void event_remove(event_t *event);
\return the number of found matches
*/
int event_get(event_t *criterion, std::vector<event_t *> *out);
int event_get(const event_t &criterion, std::vector<event_t *> *out);
/**
Returns whether an event listener is registered for the given signal.
@ -168,13 +169,11 @@ void event_free(event_t *e);
/**
Returns a string describing the specified event.
*/
wcstring event_get_desc(const event_t *e);
wcstring event_get_desc(const event_t &e);
/**
Fire a generic event with the specified name
*/
#define event_fire_generic( ... ) event_fire_generic_internal( __VA_ARGS__, NULL )
void event_fire_generic_internal(const wchar_t *name,...);
void event_fire_generic(const wchar_t *name, wcstring_list_t *args = NULL);
#endif

View file

@ -199,7 +199,7 @@ void function_add(const function_data_t &data, const parser_t &parser)
/* Add event handlers */
for (std::vector<event_t>::const_iterator iter = data.events.begin(); iter != data.events.end(); ++iter)
{
event_add_handler(&*iter);
event_add_handler(*iter);
}
}
@ -229,7 +229,7 @@ static bool function_remove_ignore_autoload(const wcstring &name)
{
event_t ev(EVENT_ANY);
ev.function_name=name;
event_remove(&ev);
event_remove(ev);
}
return erased;

View file

@ -2077,6 +2077,7 @@ int parser_t::parse_job(process_t *p,
int tmp;
const wchar_t *cmd = args.at(0).completion.c_str();
wcstring_list_t event_args;
/*
We couldn't find the specified command.
@ -2157,7 +2158,9 @@ int parser_t::parse_job(process_t *p,
current_tokenizer_pos=tmp;
job_set_flag(j, JOB_SKIP, 1);
event_fire_generic(L"fish_command_not_found", (wchar_t *)(args.at(0).completion.c_str()));
event_args.push_back(args.at(0).completion);
event_fire_generic(L"fish_command_not_found", &event_args);
proc_set_last_status(err==ENOENT?STATUS_UNKNOWN_COMMAND:STATUS_NOT_EXECUTABLE);
}
}
@ -3795,7 +3798,7 @@ if_block_t::if_block_t() :
{
}
event_block_t::event_block_t(const event_t *evt) :
event_block_t::event_block_t(const event_t &evt) :
block_t(EVENT),
event(evt)
{

View file

@ -158,8 +158,8 @@ struct if_block_t : public block_t
struct event_block_t : public block_t
{
const event_t * const event;
event_block_t(const event_t *evt);
event_t const &event;
event_block_t(const event_t &evt);
};
struct function_block_t : public block_t

View file

@ -162,7 +162,6 @@ static std::vector<int> interactive_stack;
void proc_init()
{
proc_push_interactive(0);
event.arguments.reset(new wcstring_list_t);
}
@ -194,7 +193,6 @@ void job_free(job_t * j)
void proc_destroy()
{
event.arguments.reset(NULL);
job_list_t &jobs = parser_t::principal_parser().job_list();
while (! jobs.empty())
{
@ -603,11 +601,11 @@ void proc_fire_event(const wchar_t *msg, int type, pid_t pid, int status)
event.type=type;
event.param1.pid = pid;
event.arguments->push_back(msg);
event.arguments->push_back(to_string<int>(pid));
event.arguments->push_back(to_string<int>(status));
event.arguments.push_back(msg);
event.arguments.push_back(to_string<int>(pid));
event.arguments.push_back(to_string<int>(status));
event_fire(&event);
event.arguments->resize(0);
event.arguments.resize(0);
}
int job_reap(bool interactive)

1
tests/test9.err Normal file
View file

@ -0,0 +1 @@
emit: expected event name

28
tests/test9.in Normal file
View file

@ -0,0 +1,28 @@
# Test events.
# This pattern caused a crash; github issue #449
set -g var before
function test1 --on-event test
set -g var $var:test1
functions -e test2
end
function test2 --on-event test
# this should not run, as test2 gets removed before it has a chance of running
set -g var $var:test2a
end
emit test
echo $var
function test3 --on-event test3
echo received event test3 with args: $argv
end
emit test3 foo bar
# test empty argument
emit

2
tests/test9.out Normal file
View file

@ -0,0 +1,2 @@
before:test1
received event test3 with args: foo bar

1
tests/test9.status Normal file
View file

@ -0,0 +1 @@
1