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).
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.
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
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.
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", .{});
}
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.