mirror of
git://git.psyced.org/git/psyclpc
synced 2024-08-15 03:20:16 +00:00
686 lines
18 KiB
C
686 lines
18 KiB
C
// This is for crash purposes only. Don't run it in a production MUD.
|
|
//
|
|
// It may still depend on some UNItopia stuff, I haven't tested it with other
|
|
// libs. If you're using the UNItopia Mudlib to run it, you have to remove all
|
|
// nomask simul efuns (empty the file /secure/simul_efun/secure.inc), and
|
|
// unlock all privilege violations in /secure/master/compiler_control.inc by
|
|
// adding return 1; of the function.
|
|
//
|
|
// ------------------------------------------------------------------
|
|
// File: crashleak.c
|
|
// Description: Try to crash the driver and/or find memory leaks
|
|
// by calling EFuns and Operators with random arguments.
|
|
// Based on my old crasher.c
|
|
// Author: Menaures@UNItopia (2004-07-17)
|
|
//
|
|
|
|
/* --- Documentation: --- */
|
|
|
|
/* --- Inherits: --- */
|
|
|
|
/* --- Includes: --- */
|
|
#include <rtlimits.h>
|
|
|
|
// Replace this with some kind of output if you
|
|
// want Debug-Messages
|
|
#define DEBUG(x) {}
|
|
|
|
/* --- Defines: --- */
|
|
|
|
// Undef the following if you're not interested in Memleaks
|
|
#define FIND_MEMORY_LEAKS
|
|
|
|
// Where shall we write our data to?
|
|
#define CRASHLEAK_DIR "/save/crashleak/"
|
|
|
|
// Where is the doc/efun directory of the driver package?
|
|
#define DOC_EFUN_DIR CRASHLEAK_DIR"doc/efun/"
|
|
|
|
#define FILE(x,y) (CRASHLEAK_DIR+l_time+"_"+sprintf("%'0'6d_%s", (y), (x)))
|
|
#define GC_FILE(x) FILE("GC_"+(x), ++filecounter)
|
|
#define RECORD_FILE(x) FILE("REC_"+(x), ++filecounter)
|
|
#define LEAK_FILE(x) FILE("LEAK_"+(x), ++filecounter)
|
|
#define LOG_FILE(x) FILE("LOG_"+(x), ++filecounter)
|
|
|
|
// How many calls shall we do per step?
|
|
#define CALLS_PER_STEP 1000
|
|
|
|
// How long to wait between steps?
|
|
// Careful, Memleak detection might not work for lower values!
|
|
#define STEP_PAUSE 4
|
|
|
|
// Sometimes objects get removed out of the values array.
|
|
// Do you want to reinitialize it every X steps?
|
|
// For maximum Memleak Find reliability, set to 1,
|
|
// which causes reinitialization in every step (halfing
|
|
// the speed)
|
|
#define REINIT_EVERY_N_STEPS 10
|
|
|
|
// This defines minimum and maximum number of arguments per
|
|
// function call. Recorded files may still be executed with
|
|
// old values if you change this.
|
|
#define MIN_PARAMS 0
|
|
#define MAX_PARAMS 15
|
|
|
|
// NOTE:
|
|
// When looking for Memleaks, only one step per PAUSE will be made.
|
|
// Thus, detecting all Memleaks of one real step will require
|
|
// a minimum of CALLS_PER_STEP*STEP_PAUSE time until the next step
|
|
// is done with full stress speed again. This is probably a long
|
|
// time, however: if there are few leaks, lower CALLS_PER_STEP
|
|
// will only add up to the time it takes to find one in the first
|
|
// place...
|
|
|
|
/* --- Global Variables: --- */
|
|
|
|
int l_time = time(); // Just as a unique number...
|
|
mixed * efun; // EFun- and Operator-Closures. Either #'foo or ({#'foo, min_param, max_param})
|
|
mixed * value; // Value-set which contains all Data Types.
|
|
int stepcounter; // For reinit after N steps.
|
|
int filecounter; // For file naming.
|
|
mixed * queue; // Queue, to have one call-out-loop instead of many
|
|
#ifdef FIND_MEMORY_LEAKS
|
|
mixed * leakage; // To track GCs and Execs for Leak detection.
|
|
#endif
|
|
|
|
/* --- Prototypes: --- */
|
|
|
|
void crash(string file, closure callback);
|
|
void leak(closure callback);
|
|
|
|
#ifdef FIND_MEMORY_LEAKS
|
|
void check_for_leak(string file, mixed * exec);
|
|
void step_by_step(string gc_file, string file, mixed * exec);
|
|
#endif
|
|
|
|
/* --- Functions: --- */
|
|
|
|
/* --- Queue --- */
|
|
|
|
// Basic Queue, mainly to prevent recursion problems.
|
|
// And to make call_out loops easier (we have more than one)
|
|
|
|
mixed * query_queue() { return queue; }
|
|
|
|
int push(int pos, varargs mixed * what)
|
|
{
|
|
if(!pointerp(queue))
|
|
{
|
|
queue = ({});
|
|
}
|
|
|
|
if(pos <= 0)
|
|
{
|
|
pos = sizeof(queue) + 1;
|
|
}
|
|
|
|
queue = queue[0..pos-2] + ({ what }) + queue[pos-1..];
|
|
}
|
|
|
|
int pop()
|
|
{
|
|
mixed q;
|
|
|
|
if(pointerp(queue) && sizeof(queue))
|
|
{
|
|
q = queue[0];
|
|
queue = queue[1..];
|
|
apply(q[0],q[1..]);
|
|
return sizeof(queue);
|
|
}
|
|
}
|
|
|
|
void step()
|
|
{
|
|
if(set_next_reset(0) > 300)
|
|
{
|
|
set_next_reset(150);
|
|
}
|
|
|
|
pop();
|
|
call_out(#'step, STEP_PAUSE);
|
|
}
|
|
|
|
/* --- Crashleak Initialization: --- */
|
|
|
|
// Just to provide some LFun Closures below...
|
|
string foo() { return "foo"; }
|
|
mixed bar(mixed x, mixed y, mixed z) { return ({z,y,x}); }
|
|
|
|
mixed query_efun() { return efun; }
|
|
|
|
void init_efun()
|
|
{
|
|
DEBUG("init_efun()");
|
|
// Generate list of all EFuns. /doc/efun/ is part of the driver package.
|
|
// If it isn't part of your mudlib directory, copy it somewhere in it
|
|
// and modify the path accordingly.
|
|
string * list = get_dir(DOC_EFUN_DIR"*") - ({".","..","[]"});
|
|
|
|
// Convert them to EFun closures using a restore_value hack.
|
|
|
|
efun = restore_value("#1:0\n({#e:"+implode(list, ",#e:")+",})\n");
|
|
|
|
// Kill unrecognized and dangerous EFuns.
|
|
efun -= ({0,
|
|
#'efun::set_driver_hook, // it makes the crasher stop running
|
|
#'efun::limited, // too much fun
|
|
#'efun::set_this_object, // poses problems
|
|
#'efun::garbage_collection, // Can't find no leaks otherwise
|
|
#'efun::shutdown, // Can't find no crashes otherwise
|
|
#'efun::set_limits}); // Messes things up naturally
|
|
|
|
// Add Operator closures. Have not found a way to generate this list
|
|
// automatically yet, so you might want to check if it's still up2date.
|
|
// See lex.c ~ approx line 1000 for a list (this one's of 2004/07/17).
|
|
efun +=
|
|
({ #'+= ,#'++ ,#'+ ,#'-= ,#'-- ,#'- ,#'*= ,#'* ,#'/=
|
|
,#'/ ,#'%= ,#'% ,#', ,#'^= ,#'^ ,#'|| ,#'||= ,#'|=
|
|
,#'| ,#'&& ,#'&&= ,#'&= ,#'& ,#'~ ,#'<= ,#'<<= ,#'<<
|
|
,#'< ,#'>= ,#'>>= ,#'>>>= ,#'>>> ,#'>> ,#'> ,#'== ,#'=
|
|
,#'!= ,#'! ,#'?! ,#'? ,#'[..] ,#'[..<] ,#'[<..]
|
|
,#'[<..<] ,#'[..>] ,#'[>..] ,#'[<..>]
|
|
,#'[>..<] ,#'[>..>] ,#'[.. ,#'[<..
|
|
,#'[>.. ,#'[,] ,#'[ ,#'[< ,#'[>
|
|
,#'({ ,#'([ ,#'-> ,#'(<
|
|
});
|
|
}
|
|
|
|
mixed query_values() { return value; }
|
|
|
|
struct Bar {
|
|
mixed four;
|
|
};
|
|
|
|
struct Foo {
|
|
int one, *two;
|
|
struct Bar three;
|
|
};
|
|
|
|
void init_value()
|
|
{
|
|
DEBUG("init_value()");
|
|
|
|
// The more values, the more variation, the better.
|
|
// Feel free to add stuff to this list. However, don't modify it
|
|
// on the fly, changing this array will make recorded files useless.
|
|
|
|
// Please refrain from using self-referencing structures unless you
|
|
// want to test the crash aspect only. Include self-referencing
|
|
// stuff inside #ifndef FIND_MEMORY_LEAKS #endif blocks only.
|
|
|
|
if(pointerp(value))
|
|
{
|
|
// Do some cleanup first:
|
|
map(value, (: objectp($1) && destruct($1) :));
|
|
}
|
|
|
|
value = 0;
|
|
value =
|
|
({
|
|
/* --- Integer: --- */
|
|
-16,-15,-14,-13,-12,-11,-10,-9,-8,-7,-6,-5,-4,-3,-2,-1,
|
|
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,
|
|
-1000,1000,-50000,50000,-2000000,2000000,
|
|
__INT_MIN__+16,__INT_MAX__-16,__INT_MIN__,__INT_MAX__,
|
|
|
|
/* --- Float: --- */
|
|
0.0,0.001,-0.001,-0.5,0.5,0.99,-0.99,1.01,-1.01,
|
|
42.767,-42.767,1.0,2.0,3.0,-1.0,-2.0,-3.0,
|
|
to_float(__INT_MIN__), to_float(__INT_MAX__),
|
|
1.0e+100, -1.0e+100,
|
|
|
|
/* --- String: --- */
|
|
"", " ", " ", "!_!_", "_!_!",
|
|
"foo","bar","foo bar", "%d", "%Q", "0", "^^^^^^",
|
|
"#^# #^#", " ^",
|
|
"^ ", " - - - - ",
|
|
"? ? ? ? ? ? ????? ???? ??", "",
|
|
"\\/ "*32, " !"*42, "/", "/..",
|
|
"/a", "/d/", "/w/..",
|
|
"%#Q%#Q%#Q","%d%-=80s%%",
|
|
save_value(efun), save_object(),
|
|
|
|
/* --- Object: --- */
|
|
|
|
// This is mudlib specific, please provide some objects;
|
|
// Preferred are Blueprints, Clones, Objects with Environment,
|
|
// and Objects which have other objects inside (rooms).
|
|
//
|
|
// Note that random calls (for example to destruct()) might
|
|
// remove objects; so they should be reloaded here.
|
|
// A proper cleanup of old objects then would probably be good too.
|
|
load_object("/obj/fackel"), clone_object("/obj/fackel"),
|
|
load_object("/obj/player"), clone_object("/obj/player"),
|
|
load_object("/i/item/message"),
|
|
clone_object("/obj/rucksack"), clone_object("/obj/kurstafel"),
|
|
clone_object("/obj/wizard_shell"), clone_object("/secure/obj/login"),
|
|
load_object("/secure/obj/login"), load_object("/apps/error_db"),
|
|
load_object("/apps/plugin"), clone_object("/obj/zauberstab"),
|
|
load_object("/room/church"), load_object("/room/void"),
|
|
load_object("/room/hell"), load_object("/room/death/death"),
|
|
load_object("/room/rathaus/treppe"),
|
|
load_object("/room/rathaus/konferenz"),
|
|
load_object("/room/rathaus/forum"),
|
|
clone_object("/obj/monster"), clone_object("/obj/monster"),
|
|
clone_object("/obj/monster"), clone_object("/obj/monster"),
|
|
|
|
/* --- Closure: --- */
|
|
(: :), (: 1 :), (: 0 :), (: $2/$1 :),
|
|
#'efun::max, #'efun::input_to, #'efun::this_player,
|
|
#'efun::this_object, #'efun::call_out,
|
|
symbol_function("query_long", load_object("/i/item")),
|
|
symbol_function("query_real_name", load_object("/i/player/player")),
|
|
#'foo, #'bar,
|
|
function { return 2 * $1; },
|
|
function int (int a) { return 2 * a; },
|
|
function (int a) { return 2 * a; },
|
|
lambda( ({'x, 'y}), ({#'?, ({#'>, 'x, 'y}), 'x, 'y}) ),
|
|
unbound_lambda( ({'x}), ({#'==, 0, 'x}) ),
|
|
(: for(;;); :), (: while($1); :),
|
|
|
|
/* --- Array: --- */
|
|
({}), ({ ({}) }), ({ (: :) }), ({ 0 }), ({ 1 }),
|
|
({ "" }), ({ ([]) }), ({#'efun::min}),
|
|
allocate(5), allocate(10,1), allocate(query_limits(1)[LIMIT_ARRAY]),
|
|
({'x, 'y}), ({#'?, ({#'>, 'x, 'y}), 'x, 'y}),
|
|
({'x}), ({#'==, 0, 'x}),
|
|
|
|
/* --- Mapping: --- */
|
|
([:0]), ([:1]), ([:2]), (["a":"b"]),([([1]):({2,3})]),
|
|
mkmapping( ({1,2,3,4,5,6,7,8,9,10}), ({1,2,3,4,5,6,7,8,9,10}) ),
|
|
mkmapping( ({"1","2","3","4","5","6","7","8","9","10"}) ),
|
|
([ load_object("/apps/groups") ]),
|
|
([ #'efun::allocate ]),
|
|
|
|
/* --- Symbol / Quoted: --- */
|
|
'a, 'b, 'c, quote(allocate(10)),
|
|
'dont_know_what_this_is_supposed_to_be_it_sure_is_fun,
|
|
'whatever_floats_your_boat,
|
|
|
|
/* --- Struct: --- */
|
|
});
|
|
|
|
// --- Lets add some special values. ---
|
|
|
|
// Container inside Container inside...
|
|
object ob = clone_object("/obj/rucksack");
|
|
|
|
|
|
for(int i = 10; i--; )
|
|
{
|
|
object ob2 = clone_object("/obj/rucksack");
|
|
ob->move(ob2);
|
|
ob=ob2;
|
|
}
|
|
|
|
value += ({ob});
|
|
|
|
// Some random strings. Good for crashing, bad for debugging...
|
|
// So to make things reproducible, we generate them only once,
|
|
// and only load them afterwards. Delete the Savefile if you
|
|
// want new random strings.
|
|
|
|
string str;
|
|
|
|
if(str = read_file(CRASHLEAK_DIR"random_strings.o"))
|
|
{
|
|
value += restore_value(str);
|
|
}
|
|
|
|
else
|
|
{
|
|
string * random_strings = ({});
|
|
|
|
for(int i = 10; i--; )
|
|
{
|
|
int * arr;
|
|
|
|
arr = map(allocate(random(100)), (: random(256) :));
|
|
random_strings += ({ to_string(arr) });
|
|
}
|
|
|
|
// Save these strings so that we can reuse them next time.
|
|
write_file(CRASHLEAK_DIR"random_strings.o",
|
|
save_value(random_strings));
|
|
|
|
// Don't forget to add them to the values array...
|
|
value += random_strings;
|
|
}
|
|
|
|
// Structs
|
|
struct Bar foo = (<Bar> 10);
|
|
struct Foo bar = (<Foo> 1, ({"2",3}), (<Bar> 5));
|
|
|
|
value += ({foo,bar})*20;
|
|
}
|
|
|
|
mixed do_plot_efun(closure cl)
|
|
{
|
|
// DEBUG(sprintf("do_plot_efun(%#Q)",cl));
|
|
|
|
object ob = this_object();
|
|
|
|
int min_p = MIN_PARAMS;
|
|
int max_p = MAX_PARAMS;
|
|
|
|
for(int p = MAX_PARAMS; p >= MIN_PARAMS; p--)
|
|
{
|
|
|
|
|
|
string str = catch(apply(cl, allocate(p)); nolog);
|
|
|
|
// DEBUG(sprintf(" str -> %#Q", str));
|
|
|
|
if(str && strstr(str, "many arguments") != -1)
|
|
{
|
|
max_p--;
|
|
}
|
|
|
|
else if(str && strstr(str, "few arguments") != -1)
|
|
{
|
|
min_p++;
|
|
}
|
|
}
|
|
|
|
set_this_object(ob);
|
|
seteuid(0);
|
|
seteuid(getuid());
|
|
|
|
if(min_p != MIN_PARAMS || max_p != MAX_PARAMS)
|
|
{
|
|
return ({cl, min_p, max_p});
|
|
}
|
|
|
|
return cl;
|
|
}
|
|
|
|
void plot_efun()
|
|
{
|
|
DEBUG("plot_efun()");
|
|
|
|
efun = filter(efun, #'closurep);
|
|
efun = limited(#'map, ({10000000}), efun, #'do_plot_efun);
|
|
|
|
garbage_collection(GC_FILE("plot_efun"));
|
|
}
|
|
|
|
void init_crashleak(closure callback)
|
|
{
|
|
DEBUG("init_crashleak()");
|
|
|
|
// Sometimes init fails. Recall.
|
|
push(1, #'init_crashleak, callback);
|
|
|
|
string str;
|
|
|
|
if(str = catch(limited(#'init_efun, ({1000000})); publish))
|
|
{
|
|
debug_message("CRASHLEAK: init_efun failed:\n"+str+"\n");
|
|
return;
|
|
}
|
|
|
|
if(str = catch(limited(#'init_value, ({1000000})); publish))
|
|
{
|
|
debug_message("CRASHLEAK: init_value failed:\n"+str+"\n");
|
|
}
|
|
|
|
garbage_collection(GC_FILE("AFTER_INIT"));
|
|
|
|
// Del the old entry
|
|
queue = queue[1..];
|
|
push(0, callback);
|
|
|
|
// Determine EFun parameter counts next step.
|
|
push(1, #'plot_efun);
|
|
}
|
|
|
|
void crashleak()
|
|
{
|
|
DEBUG("crashleak()");
|
|
|
|
if(stepcounter++ == REINIT_EVERY_N_STEPS)
|
|
{
|
|
init_crashleak(#'crashleak);
|
|
stepcounter=0;
|
|
return;
|
|
}
|
|
|
|
|
|
#ifdef FIND_MEMORY_LEAKS
|
|
push(0, #'crash, RECORD_FILE("cl"), #'check_for_leak);
|
|
#else
|
|
push(0, #'crash, RECORD_FILE("cl"), #'crashleak);
|
|
#endif
|
|
}
|
|
|
|
/* --- Crash Execution: --- */
|
|
|
|
mixed * pick()
|
|
{
|
|
DEBUG("pick()");
|
|
|
|
mixed * erg = ({});
|
|
|
|
int f, p;
|
|
|
|
for(int i = CALLS_PER_STEP; i--; )
|
|
{
|
|
f = random(sizeof(efun));
|
|
|
|
if(closurep(efun[f]))
|
|
{
|
|
p = MIN_PARAMS + random(MAX_PARAMS-MIN_PARAMS);
|
|
}
|
|
|
|
else
|
|
{
|
|
p = efun[f][1] + random(efun[f][2]-efun[f][1]);
|
|
}
|
|
|
|
|
|
erg +=
|
|
({
|
|
// Pick EFun index.
|
|
({ random(sizeof(efun)),
|
|
// Pick p parameter indices.
|
|
map(allocate(p), (: random(sizeof(value)) :)),
|
|
// Will be replaced with a string later:
|
|
// (debugmessage of what was executed)
|
|
0
|
|
})
|
|
});
|
|
}
|
|
|
|
return erg;
|
|
}
|
|
|
|
varargs void exec(mixed * exec, int quiet)
|
|
{
|
|
DEBUG("exec()");
|
|
|
|
string file;
|
|
string text = "";
|
|
object ob = this_object();
|
|
|
|
debug_message("start_of_exec\n");
|
|
|
|
foreach(mixed * x : exec)
|
|
{
|
|
mixed f = efun[x[0]];
|
|
if(pointerp(f)) f=f[0];
|
|
mixed * v = map(x[1], (: value[$1] :));
|
|
string str = sprintf("%d %#Q\n", x[0],x[1]);
|
|
|
|
// Note: This chance affects arrays by reference. :-)
|
|
x[2] = str;
|
|
|
|
debug_message(sprintf("exec: %s",str));
|
|
|
|
set_this_object(ob);
|
|
seteuid(0);
|
|
seteuid(getuid());
|
|
catch(apply(f, v); reserve (max(get_eval_cost()/2,100000)), nolog);
|
|
|
|
if(get_eval_cost() < 100000)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
set_this_object(ob);
|
|
|
|
debug_message("end_of_exec\n");;
|
|
}
|
|
|
|
void crash(string file, closure callback)
|
|
{
|
|
DEBUG("crash()");
|
|
|
|
mixed * exec;
|
|
|
|
/* --- Pick what we will execute in this run: --- */
|
|
exec = limited(#'pick, ({1000000}));
|
|
|
|
/* --- Record it to the file. --- */
|
|
write_file(file, save_value(exec));
|
|
|
|
/* --- Execute it. --- */
|
|
limited(#'exec, ({1000000}), exec);
|
|
|
|
funcall(callback, file, exec);
|
|
}
|
|
|
|
/* --- Leak detection: --- */
|
|
|
|
int analyze(string gc_file)
|
|
{
|
|
DEBUG("analyze("+gc_file+")");
|
|
|
|
string s = read_file(gc_file);
|
|
|
|
if(!stringp(s))
|
|
{
|
|
debug_message(sprintf("CRASHLEAK: Could not read file %#Q (%#Q)!\n",gc_file,s));
|
|
return 0;
|
|
}
|
|
|
|
string * str = explode(s, "\n");
|
|
|
|
return sizeof(regexp(str, "freeing.*block"));
|
|
}
|
|
|
|
void leak_gc(string file, mixed * exec)
|
|
{
|
|
DEBUG("leak_gc( ["+sizeof(exec)+"] )");
|
|
|
|
string sbs = GC_FILE("sbs");
|
|
|
|
// GC.
|
|
garbage_collection(sbs);
|
|
// Note this file will be created later.
|
|
|
|
// Push.
|
|
push(1, #'step_by_step, sbs, file, exec);
|
|
|
|
// Execute.
|
|
limited(#'exec, ({1000000}), exec, 1);
|
|
}
|
|
|
|
void step_by_step(string gc_file, string file, mixed * exec)
|
|
{
|
|
DEBUG("step_by_step()");
|
|
|
|
// Okay. Binary search.
|
|
|
|
// Was there a leak?
|
|
if(analyze(gc_file))
|
|
{
|
|
// Whee! There was one. Let's see what we do.
|
|
|
|
// How many execs are there which might have caused the leak?
|
|
if(sizeof(exec) == 1)
|
|
{
|
|
// Only one? Okay, this is it then.
|
|
DEBUG("LEAK: "+exec[0][2]+"\n");
|
|
write_file(LEAK_FILE(explode(gc_file,"/")[<1]), exec[0][2]+"\n");
|
|
|
|
// We haven't done a GC, so we can pop:
|
|
call_out(#'pop, 0);
|
|
return;
|
|
}
|
|
|
|
// Okay. Do the first half now...
|
|
leak_gc(file, exec[..(sizeof(exec)/2)-1]);
|
|
// ...push the second half into the first queue, coz we can't do
|
|
// to garbage_collection() at once.
|
|
push(1, #'leak_gc, file, exec[(sizeof(exec)/2)..]);
|
|
}
|
|
|
|
else
|
|
{
|
|
// We haven't done a GC, so we can pop:
|
|
call_out(#'pop, 0);
|
|
}
|
|
|
|
// And wait...
|
|
}
|
|
|
|
void check_for_leak(string file, mixed * exec)
|
|
{
|
|
DEBUG("check_for_leak()");
|
|
|
|
string gc_file = GC_FILE("check_for_leak");
|
|
|
|
// Warning: This is actually executed at the end of the cycle,
|
|
// and not right now.
|
|
garbage_collection(gc_file);
|
|
// This means gc_file does not exist now yet.
|
|
|
|
// Check previous leakage (their gc_file exists now)
|
|
if(leakage && analyze(leakage[0]))
|
|
{
|
|
// We need to do a step by step further investigation
|
|
// of this exec to find which call exactly has caused
|
|
// the leak.
|
|
push(0, #'step_by_step, leakage[0], leakage[1], leakage[2]);
|
|
|
|
// Continue Crashing afterwards.
|
|
push(0, #'crashleak);
|
|
}
|
|
|
|
else
|
|
{
|
|
// nothing to do. continue crashing.
|
|
crashleak();
|
|
}
|
|
|
|
// Remember this leakage and test it in the next run.
|
|
leakage = ({gc_file,file,exec});
|
|
}
|
|
|
|
/* --- Applied LFuns: --- */
|
|
|
|
void reset()
|
|
{
|
|
if(sizeof(queue) <= 0)
|
|
{
|
|
push(0, #'init_crashleak, #'crashleak);
|
|
}
|
|
|
|
if(find_call_out(#'step) <= 0)
|
|
{
|
|
call_out(#'step, STEP_PAUSE);
|
|
}
|
|
}
|
|
|
|
void create()
|
|
{
|
|
DEBUG("create()");
|
|
|
|
garbage_collection(GC_FILE("AFTER_CREATE"));
|
|
|
|
reset();
|
|
}
|
|
|
|
/* --- End of file. --- */
|