Safety strategy

Hello fellow programmers,

I need a safe and simple way to protect my (chess) program.
Without any affect on a ReleaseFast version, which is assumed to be bug-free.

Let me explain first what I am doing: I run my chess engine with the help of a program called “CuteChess”: a cli-application with which I can play a few 100 games against another engine.
CuteChess relays the stdin and stdout. The engines “talk” to it using these with the so-called UCI-protocol.

I discovered a really terrible little bug, accessing some array with an out of bounds index.
Which made me think: it could be that there are other anomalies which can only be discovered playing many many (long) games.
So the only way is: run CuteChess with a ReleaseSafe version.

In some functions I use assert where I am quite sure the compiler can use it to generate optimized code.

In most cases I do not want to use assert because it will affect performance negatively.

In a ReleaseSafe or Debug version I need to be able to see what went wrong.
Something like:

if (releasesafe or debug) {
    const situation_is_ok = ....
    check(situation_is_ok, "omg this or that happened");
}

Would the best way to do that is create a file and write the error there? stderr is - I believe - ignored by CuteChess.

I know std.log exists. Would that be usable? (Did not dive into the code of that one).

Any smart examples / ideas?

Logging to a file IMO is always better, but then I’m not running in an “everything is in a console” world that linuxen live in.

1 Like

Assertions and crashing on failed asserts is a good strategy, overall, in my opinion. Don’t use ReleaseFast unless you have a much more sophisticated strategy which allows you to forego the runtime checks. For example, simulation testing, fuzzing, formal methods; maybe the combination of all three.

You can use @setRuntimeSafety to bypass safety-checks in performance-critical code paths which are more safety-scrutinized than the rest of the program.

2 Likes

Either panic or std.log would work, the question is whether you want to stop execution or let it continue in a potentially invalid state. I know from your previous posts that you do want to risk execution in invalid states, in ReleaseFast mode. But since this is not ReleaseFast mode, a panic may be the right choice.

P.S. I prefer fail-fast (panic) when testing and debugging, because it is more “in your face”. You don’t have to remember to check the log for errors.

Yes but panic will crash without any visual information. So log to file is needed.
And yes: when I do a release of my program i do not care if it crashes or goes on in an invalid state. I am testing testing testing before that :slight_smile:

1 Like

I did not know @setRuntimeSafety.

// The builtin applies to the scope that it is called in. So here, integer overflow
// will not be caught in ReleaseFast and ReleaseSmall modes:

Does that mean that overflows are checked in ReleaseFast?
How does that work? I did not expect that.
What is the default?

panic has parameters for formatting the output, just like print.
https://ziglang.org/documentation/master/std/#std.debug.panic

When the doc says safety checks are enabled or disabled in a particular build mode, that’s the default for @setRuntimeSafety.

You’re misreading the code in the doc. The statement you quoted is the behavior before @setRuntimeSafety is used in the following block ({}). It only applies inside the block where it is called, and not in functions called from that block.

Even for embedded std.log is useful since you can define in your root module where the messages get written to (e.g. a UART)

1 Like

Oh! I only knew the @panic().
But still that CuteChess application will not show anything except “chessnix has disconnected” :slight_smile:

-Doptimize=ReleaseFast
    Optimizations on and safety off

ok clear. thanks!

You can overwrite the panic function and e.g. dump a stacktrace:

const std = @import("std");

const panic = struct {
    pub fn call(msg: []const u8, ra: ?usize) noreturn {
        // do whatever here
        while (true) {
            @breakpoint();
        }
    }
};

Don’t know why, but you could try:

-debug
    Display all engine input and output.

or

stderr=arg
    Redirect standard error output to file arg.
1 Like

Yes. You should do that. This is what you’re looking for.

2 Likes

Panics print to stderr. You’re not getting any feedback because, presumably, your host application is throwing it all out. If you change you panic to dump into a file, like @KilianHanich suggested, or convince the host application to save you stderr somewhere, like @jumpnbrownweasel showed, then you don’t have to do anything to your code.

You could use std.log by setting std.Options.logFn to a custom function that just calls std.Io.Writer.print on the interface of a globally held std.fs.File.Writer.

Here’s a 0.15.2 code example:

// main.zig

const std = @import("std");

pub const std_options: std.Options = .{
	.logFn = fileLog,
};

fn fileLog(
	comptime message_level: std.log.Level,
	comptime scope: @Type(.enum_literal),
	comptime fmt: []const u8,
	args: anytype,
) void {
	// Panic if attempting to log before the file writer is initialised
	if(!has_logfile_writer) @panic("Attempted to write to logfile without the file having been opened");

	const scope_txt = "(" ++ @tagName(scope) ++ ") ";
	const level_txt = switch(message_level){
		.err => "error: ",
		.warn => "warn: ",
		.info => "info: ",
		.debug => "debug: ",
	};

	logfile_writer.print(scope_txt ++ level_txt ++ fmt ++ "\n", args) catch |e| {
		std.debug.panic("{t}: Failed to print to logfile", .{e});
	};
	logfile_writer.flush() catch |e| {
		std.debug.panic("{t}: Failed to write to logfile", .{e});
	};
}

var logfile_writer_buf: [4096]u8 = undefined;
var logfile_filewriter: std.fs.File.Writer = undefined;
var logfile_writer: *std.Io.Writer = undefined;
var has_logfile_writer: bool = false;

pub fn main() !void {
	// Ideally, this would be a dirname argument, not the CWD
	const logfile = try std.fs.cwd().createFile("logfile.txt", .{});
	logfile_filewriter = logfile.writer(&logfile_writer_buf);
	logfile_writer = &logfile_filewriter.interface;
	has_logfile_writer = true;

	std.log.debug("Logfile initialised", .{});
}

2 Likes

Thanks. I’ll keep that code around.
All I need is to create a log file on crash (only in bughunt mode). I think it’s easier to do that in 3 lines of code actually.: create file in the cwd and write some info. I don’t need advanced logging (which I think is the purpose of std.log).

Well, one advantage of doing this anyway would be that you (and possibly some libraries) can leave bread crumbs around which can make finding the bugs easier.

You should read it! don’t guess, there’s no reason for that. It does exactly what you want, this ain’t Java chief. :slight_smile: