Do a lot of documentation work and move some scripts

Signed-off-by: Gavin Howard <gavin@yzena.com>
computed_goto
Gavin Howard 1 year ago
parent d12825ae80
commit 10164297c5
Signed by: gavin
GPG Key ID: C08038BDF280D33E
  1. 10
      configure.sh
  2. 101
      manuals/build.md
  3. 307
      manuals/development.md
  4. 6
      manuals/release.md
  5. 54
      scripts/afl.py
  6. 3
      scripts/alloc.sh
  7. 1
      scripts/exec-install.sh
  8. 63
      scripts/functions.sh
  9. 36
      scripts/fuzz_prep.sh
  10. 2
      scripts/locale_install.sh
  11. 108
      scripts/manpage.sh
  12. 24
      scripts/package.sh
  13. 27
      scripts/radamsa.sh
  14. 0
      scripts/radamsa.txt
  15. 145
      scripts/randmath.py
  16. 147
      scripts/release.sh
  17. 10
      scripts/test_settings.sh
  18. 9
      tests/all.sh

@ -345,6 +345,9 @@ replace_exts() {
# needed for the chosen build. Below, you will see a lot of calls to this
# function.
#
# Note that needle can never contain an exclamation point. For more information,
# see substring_replace() in scripts/functions.sh.
#
# @param str The string to find and replace placeholders in.
# @param needle The placeholder name.
# @param replacement The string to use to replace the placeholder.
@ -449,7 +452,7 @@ gen_tests() {
_gen_tests_time_tests="$1"
shift
_gen_tests_extra_required=$(cat tests/extra_required.txt)
_gen_tests_extra_required=$(cat "$scriptdir/tests/extra_required.txt")
for _gen_tests_t in $(cat "$scriptdir/tests/$_gen_tests_name/all.txt"); do
@ -1173,7 +1176,10 @@ if [ "$nls" -ne 0 ]; then
printf 'gencat works.\n\n'
if [ "$HOSTCC" != "$CC" ]; then
# It turns out that POSIX locales are really terrible, and running
# gencat on one machine is not guaranteed to make those cat files
# portable to another machine, so we had better warn the user here.
if [ "$HOSTCC" != "$CC" ] || [ "$HOSTCFLAGS" != "$CFLAGS" ]; then
printf 'Cross-compile detected.\n\n'
printf 'WARNING: Catalog files generated with gencat may not be portable\n'
printf ' across different architectures.\n\n'

@ -428,22 +428,6 @@ another platform that does not support the POSIX locale API or utilities.
This option affects the [build type][7].
#### Locales
By default, `bc` and `dc` do not install all locales, but only the enabled
locales. If `DESTDIR` exists and is not empty, then they will install all of
the locales that exist on the system. The `-l` flag or `--install-all-locales`
option skips all of that and just installs all of the locales that `bc` and `dc`
have, regardless. To enable that behavior, you can pass the `-l` flag or the
`--install-all-locales` option to `configure.sh`, as follows:
```
./configure.sh -l
./configure.sh --install-all-locales
```
Both commands are equivalent.
#### Extra Math
This `bc` has 7 extra operators:
@ -502,6 +486,75 @@ Default is `32`.
to `16` (to prevent stack overflow). If it is not, `configure.sh` will give an
error.
#### Settings
This `bc` and `dc` have a few settings to override default behavior.
The defaults for these settings can be set by package maintainers, and the
settings themselves can be overriden by users.
To set a default to **on**, use the `-s` or `--set-default-on` option to
`configure.sh`, with the name of the setting, as follows:
```
./configure.sh -s bc.banner
./configure.sh --set-default-on=bc.banner
```
Both commands are equivalent.
To set a default to **off**, use the `-S` or `--set-default-off` option to
`configure.sh`, with the name of the setting, as follows:
```
./configure.sh -S bc.banner
./configure.sh --set-default-off=bc.banner
```
Both commands are equivalent.
Users can override the default settings set by packagers with environment
variables. If the environment variable has an integer, then the setting is
turned **on** for a non-zero integer, and **off** for zero.
The table of the available settings, along with their defaults and the
environment variables to override them, is below:
```
| Setting | Description | Default | Env Variable |
| =============== | ====================== | ============ | =============== |
| bc.banner | Whether to display the | 0 | BC_BANNER |
| | bc version banner when | | |
| | in interactive mode. | | |
| --------------- | ---------------------- | ------------ | --------------- |
| bc.sigint_reset | Whether SIGINT should | 1 | BC_SIGINT_RESET |
| | reset bc, instead of | | |
| | exiting, when in | | |
| | interactive mode. | | |
| --------------- | ---------------------- | ------------ | --------------- |
| dc.sigint_reset | Whether SIGINT should | 1 | DC_SIGINT_RESET |
| | reset dc, instead of | | |
| | exiting, when in | | |
| | interactive mode. | | |
| --------------- | ---------------------- | ------------ | --------------- |
| bc.tty_mode | Whether TTY mode for | 1 | BC_TTY_MODE |
| | bc should be on when | | |
| | available. | | |
| --------------- | ---------------------- | ------------ | --------------- |
| dc.tty_mode | Whether TTY mode for | 0 | BC_TTY_MODE |
| | dc should be on when | | |
| | available. | | |
| --------------- | ---------------------- | ------------ | --------------- |
| bc.prompt | Whether the prompt for | $BC_TTY_MODE | BC_PROMPT |
| | bc should be on in TTY | | |
| | mode. | | |
| --------------- | ---------------------- | ------------ | --------------- |
| dc.prompt | Whether the prompt for | $DC_TTY_MODE | DC_PROMPT |
| | dc should be on in TTY | | |
| | mode. | | |
| --------------- | ---------------------- | ------------ | --------------- |
```
#### Install Options
The relevant `autotools`-style install options are supported in `configure.sh`:
@ -545,6 +598,22 @@ To disable installing manpages, pass either the `-M` flag or the
Both commands are equivalent.
##### Locales
By default, `bc` and `dc` do not install all locales, but only the enabled
locales. If `DESTDIR` exists and is not empty, then they will install all of
the locales that exist on the system. The `-l` flag or `--install-all-locales`
option skips all of that and just installs all of the locales that `bc` and `dc`
have, regardless. To enable that behavior, you can pass the `-l` flag or the
`--install-all-locales` option to `configure.sh`, as follows:
```
./configure.sh -l
./configure.sh --install-all-locales
```
Both commands are equivalent.
### Optimization
The `configure.sh` script will accept an optimization level to pass to the

@ -539,7 +539,8 @@ Why did I implement my own buffered I/O for `bc`? Because I use `setjmp()` and
well with the use of those procedures.
For more information about `bc`'s error handling and custom buffered I/O, see
[`vm.h`][27] and the notes about version [`3.0.0`][32] in the [`NEWS`][32].
[Error Handling][97], along with [`vm.h`][27] and the notes about version
[`3.0.0`][32] in the [`NEWS`][32].
The code associated with this header is in [`src/file.c`][47].
@ -711,7 +712,7 @@ This file is a template for the markdown version of the `bc` manual and
manpages.
For more information about how the manpages and markdown manuals are generated,
and for why, see [`scripts/manpage.sh`][60].
and for why, see [`scripts/manpage.sh`][60] and [Manuals][86].
#### `bcl.3`
@ -719,7 +720,7 @@ This is the manpage for the `bcl` library. It is generated from
[`bcl.3.md`][61] using [`scripts/manpage.sh`][60].
For the reason why I check generated data into the repo, see
[`scripts/manpage.sh`][60].
[`scripts/manpage.sh`][60] and [Manuals][86].
#### `bcl.3.md`
@ -754,7 +755,7 @@ This file is a template for the markdown version of the `dc` manual and
manpages.
For more information about how the manpages and markdown manuals are generated,
and for why, see [`scripts/manpage.sh`][60].
and for why, see [`scripts/manpage.sh`][60] and [Manuals][86].
#### `development.md`
@ -785,28 +786,32 @@ TODO:
Used by [`scripts/manpage.sh`][60] to give the [`bcl.3`][62] manpage a proper
header.
For more information, see [`scripts/manpage.sh`][60].
For more information about generating manuals, see [`scripts/manpage.sh`][60]
and [Manuals][86].
#### `header_bc.txt`
Used by [`scripts/manpage.sh`][60] to give the [generated `bc` manpages][79] a
proper header.
For more information, see [`scripts/manpage.sh`][60].
For more information about generating manuals, see [`scripts/manpage.sh`][60]
and [Manuals][86].
#### `header_dc.txt`
Used by [`scripts/manpage.sh`][60] to give the [generated `dc` manpages][80] a
proper header.
For more information, see [`scripts/manpage.sh`][60].
For more information about generating manuals, see [`scripts/manpage.sh`][60]
and [Manuals][86].
#### `header.txt`
Used by [`scripts/manpage.sh`][60] to give all generated manpages a license
header.
For more information, see [`scripts/manpage.sh`][60].
For more information about generating manuals, see [`scripts/manpage.sh`][60]
and [Manuals][86].
#### `release.md`
@ -839,6 +844,21 @@ This folder contains helper scripts. Most of them are written in pure [POSIX
For more information about the shell scripts, see [POSIX Shell Scripts][76].
#### `afl.py`
This script is meant to be used as part of the fuzzing workflow.
It does one of two things: checks for valid crashes, or runs `bc` and or `dc`
under all of the paths found by AFL++.
See [Fuzzing][82] for more information about fuzzing, including this script.
#### `alloc.sh`
This script is a quick and dirty script to test whether or not the garbage
collection mechanism of the [`BcNum` caching][96] works. It has been little-used
because it tests something that is not important to correctness.
#### `exec-install.sh`
This script is the magic behind making sure `dc` is installed properly if it's
@ -872,11 +892,16 @@ For more information about fuzzing, see [Fuzzing][82].
#### `karatsuba.py`
This script has two major differences from most of the other scripts:
This script has at least one of two major differences from most of the other
scripts:
* It's in Python 3.
* It's meant for software packagers.
For example, [`scripts/afl.py`][94] and [`scripts/randmath.py`][95] are both in
Python 3, but they are not meant for the end user or software packagers and are
not included in source distributions. But this script is.
This script breaks my rule of only POSIX utilities necessary for package
maintainers, but there's a very good reason for that: it's only meant to be run
*once* when the package is created for the first time, and maybe not even then.
@ -917,13 +942,8 @@ you are supposed to install locales.
Yes, *how*. And where.
Obviously, from it's name, it's a path, and that's the where. The *how* is more
complicated.
It's actually *not* a path, but a path template. It's a format string, and it
can have a few format specifiers. For more information on that, see [this link][84].
For more information on locales, see [Locales][85].
But now is not the place to rant about `$NLSPATH`. For more information on
locales and `$NLSPATH`, see [Locales][85].
#### `locale_uninstall.sh`
@ -937,7 +957,132 @@ For more information on locales, see [Locales][85].
#### `manpage.sh`
This script is the one that generates markdown manuals from a template and a
manpage from a markdown manual.
For more information about generating manuals, see [Manuals][86].
#### `package.sh`
This script is what helps `bc` maintainers cut a release. It does the following:
1. Creates the appropriate `git` tag.
2. Pushes the `git` tag.
3. Copies the repo to a temp directory.
4. Removes files that should not be included in source distributions.
5. Creates the tarballs.
6. Signs the tarballs.
7. Zips and signs the Windows executables if they exist.
8. Calculates and outputs SHA512 and SHA256 sums for all of the files,
including the signatures.
This script is for `bc` maintainers to use when cutting a release. It is not
meant for outside use. This means that some non-POSIX utilities can be used,
such as `git` and `gpg`.
In addition, before using this script, it expects that the folders that Windows
generated when building `bc`, `dc`, and `bcl`, are in the parent directory of
the repo, exactly as Windows generated them. If they are not there, then it will
not zip and sign, nor calculate sums of, the Windows executables.
Because this script creates a tag and pushes it, it should *only* be run *ONCE*
per release.
#### `radamsa.sh`
A script to test `bc`'s command-line expression parsing code, which, while
simple, strives to handle as much as possible.
What this script does is it uses the test cases in [`radamsa.txt`][98] an input
to the [Radamsa fuzzer][99].
The reason I use [Radamsa][99] instead of AFL++ is because it is easier to use
with varying command-line arguments, which is what's needed here. (AFL++ is best
when testing input from `stdin`.)
This script does also do fuzzing on the AFL++ inputs, but it's not as effective
at that, so I don't really use it for that either.
This script was only really used once; I have not had to touch the command-line
expression parsing code since.
#### `radamsa.txt`
Initial test cases for the [`radamsa.sh`][100] script.
#### `randmath.py`
This script generates random math problems and checks that `bc`'s and `dc`'s
output matches the GNU `bc` and `dc`. (For this reason, it is necessary to have
GNU `bc` and `dc` installed before using this script.)
One snare: be sure that this script is using the GNU `bc` and `dc`, not a
previously-installed version of this `bc` and `dc`.
If you want to check for memory issues or failing asserts, you can build the
`bc` using `./scripts/fuzz_prep.sh -a`, and then run it under this script. Any
errors or crashes should be caught by the script and given to the user as part
of the "checklist" (see below).
The basic idea behind this script is that it generates as many math problems as
it can, biasing towards situations that may be likely to have bugs, and testing
each math problem against GNU `bc` or `dc`.
If GNU `bc` or `dc` fails, it just continues. If this `bc` or `dc` fails, it
stores that problem. If the output mismatches, it also stores the problem.
Then, when the user sends a `SIGINT`, the script stops testing and goes into
report mode. One-by-one, it will go through the "checklist," the list of failed
problems, and present each problem to the user, as well as whether this `bc` or
`dc` crashed, and its output versus GNU. Then the user can decide to add them as
test cases, which it does automatically to the appropriate test file.
#### `release_settings.txt`
A text file of settings combinations that [`release.sh`][83] uses to ensure that
`bc` and `dc` build and work with various default settings. [`release.sh`][83]
simply reads it line by line and uses each line for one build.
#### `release.sh`
This script is for `bc` maintainers only. It runs `bc`, `dc`, and `bcl` through
a gauntlet that is mostly meant to be used in preparation for a release.
It does the following:
1. Builds every build type, with every setting combo in
[`release_settings.txt`][93] with both calculators, `bc` alone, and `dc`
alone.
2. Builds every build type, with every setting combo in
[`release_settings.txt`][93] with both calculators, `bc` alone, and `dc`
alone for 32-bit.
3. Does #1 and #2 for Debug, Release, Release with Debug Info, and Min Size
Release builds.
4. Runs the test suite on every build, if desired.
5. Runs the test suite under [ASan, UBSan, and MSan][21] for every build
type/setting combo.
6. Runs [`scripts/karatsuba.py`][78] in test mode.
7. Runs the test suite for both calculators, `bc` alone, and `dc` alone under
[valgrind][20] and errors if there are any memory bugs or memory leaks.
#### `safe-install.sh`
A script copied from [musl][101] to atomically install files.
#### `test_settings.sh`
A quick and dirty script to help automate rebuilding while manually testing the
various default settings.
This script uses [`test_settings.txt`][103] to generate the various settings
combos.
For more information about settings, see [Settings][102] in the [build
manual][14].
#### `test_settings.txt`
A list of the various settings combos to be used by [`test_settings.sh`][104].
### `src/`
@ -979,12 +1124,116 @@ _<function_name>_<var_name>
```
This is done to prevent any clashes of variable names with already existing
names. And this applies to *all* shell scripts.
names. And this applies to *all* shell scripts. However, there are a few times
when that naming convention is *not* used; all of them are because those
functions are required to change variables in the parent script.
### Maintainer-Only Scripts
If a script is meant to be used for maintainers (of `bc`, not package
maintainers), then rules 2, 3, and 4 don't need to be followed as much because
it is assumed that maintainers will be able to install whatever tools are
necessary to do the job.
## Manuals
The manuals for `bc` are all generated, and the manpages for `bc`, `dc`, and
`bcl` are also generated.
Why?
I don't like the format of manpages, and I am not confident in my ability to
write them. Also, they are not easy to read on the web.
So that explains why `bcl`'s manpage is generated from its markdown version. But
why are the markdown versions of the `bc` and `dc` generated?
Because the content of the manuals needs to change based on the [build
type][81]. For example, if `bc` was built with no history support, it should not
have the **COMMAND LINE HISTORY** section in its manual. If it did, that would
just confuse users.
So the markdown manuals for `bc` and `dc` are generated from templates
([`manuals/bc.1.md.in`][89] and [`manuals/dc.1.md.in`][90]). And from there,
the manpages are generated from the generated manuals.
The generated manpage for `bcl` ([`manuals/bcl.3`][62]) is checked into version
control, and the generated markdown manuals and manpages for `bc`
([`manuals/bc`][91]) and `dc` ([`manuals/dc`][92]) are as well.
This is because generating the manuals and manpages requires a heavy dependency
that only maintainers should care about: [Pandoc][92]. Because users should not
have to install *any* dependencies, the files are generated, checked into
version control, and included in distribution tarballs.
For more on how generating manuals and manpages works, see
[`scripts/manpage.sh`][60].
## Locales
The locale system of `bc` is enormously complex, but that's because
POSIX-compatible locales are terrible.
How are they terrible?
First, `gencat` does not work for generating cross-compilation. In other words,
it does not generate machine-portable files. There's nothing I can do about
this except for warn users.
Second, the format of `.msg` files is...interesting. Thank goodness it is text
because otherwise, it would be impossible to get them right.
Third, `.msg` files are not used. In other words, `gencat` exists. Why?
Fourth, `$NLSPATH` is an awful way to set where and *how* to install locales.
Yes, where and *how*.
Obviously, from it's name, it's a path, and that's the where. The *how* is more
complicated.
It's actually *not* a path, but a path template. It's a format string, and it
can have a few format specifiers. For more information on that, see [this
link][84]. But in essence, those format specifiers configure how each locale is
supposed to be installed.
With all those problems, why use POSIX locales? Portability, as always. I can't
assume that `gettext` will be available, but I *can* pretty well assume that
POSIX locales will be available.
The locale system of `bc` includes all files under [`locales/`][85],
[`scripts/locale_install.sh`][87], [`scripts/locale_uninstall.sh`][88],
[`scripts/functions.sh`][105], and the parts of the build system needed to
activate it. There is also code in [`src/vm.c`][58] for loading the current
locale.
## Fuzzing
## Code Concepts
This section is about concepts that, if understood, will make it easier to
understand the code as it is written.
The concepts in this section are not found in a single source file, but they are
littered throughout the code. That's why I am writing them all down in a single
place.
### Error Handling
### Lexing
### Parsing
### Bytecode
#### Bytecode Indices
### Function Pointers
### Strings as Numbers
### Caching of Numbers
[1]: https://en.wikipedia.org/wiki/Bus_factor
[2]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/bc.html#top
[3]: https://en.wikipedia.org/wiki/Dc_(Unix)
@ -1066,7 +1315,27 @@ names. And this applies to *all* shell scripts.
[79]: #bc
[80]: #dc
[81]: ./build.md#build-type
[82]: #fuzzine
[83]: ../scripts/release.sh`
[82]: #fuzzing
[83]: #releasesh
[84]: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html#tag_08_02
[85]: #locales
[86]: #manuals
[87]: #locale_installsh
[88]: #locale_uninstallsh
[89]: #bc1mdin
[90]: #dc1mdin
[91]: #bc
[92]: https://pandoc.org/
[93]: #release_settingstxt
[94]: #aflpy
[95]: #randmathpy
[96]: #caching-of-numbers
[97]: #error-handling
[98]: #radamsatxt
[99]: https://gitlab.com/akihe/radamsa
[100]: #radamsash
[101]: https://musl.libc.org/
[102]: ./build.md#settings
[103]: #test_settingstxt
[104]: #test_settingssh
[105]: #functionssh

@ -3,15 +3,15 @@
This is the checklist for cutting a release.
For a lot of these steps, they are only needed if the code that would be
affected was changed. For example, I don't need to run the `randmath.py`
affected was changed. For example, I don't need to run the `scripts/randmath.py`
test if I did not change any of the math code.
1. Update the README.
2. Update the manuals.
3. Test history manually.
4. Test with POSIX test suite.
5. Run the randmath.py script an excessive amount and add failing tests to
test suite.
5. Run the `scripts/randmath.py` script an excessive amount and add failing
tests to test suite.
* debug
* release
* minrelease

@ -32,10 +32,23 @@ import sys
import shutil
import subprocess
# Print the usage and exit with an error.
def usage():
print("usage: {} [--asan] dir [results_dir [exe options...]]".format(script))
print(" The valid values for dir are: 'bc1', 'bc2', 'bc3', and 'dc'.")
sys.exit(1)
# Check for a crash.
# @param exebase The calculator that crashed.
# @param out The file to copy the crash file to.
# @param error The error code (negative).
# @param file The crash file.
# @param type The type of run that caused the crash. This is just a string
# that would make sense to the user.
# @param test The contents of the crash file, or which line caused the crash
# for a run through stdin.
def check_crash(exebase, out, error, file, type, test):
if error < 0:
print("\n{} crashed ({}) on {}:\n".format(exebase, -error, type))
@ -45,6 +58,21 @@ def check_crash(exebase, out, error, file, type, test):
print("\nexiting...")
sys.exit(error)
# Runs a test. This function is used to ensure that if a test times out, it is
# discarded. Otherwise, some tests result in incredibly long runtimes. We need
# to ignore those.
#
# @param cmd The command to run.
# @param exebase The calculator to test.
# @param tout The timeout to use.
# @param indata The data to push through stdin for the test.
# @param out The file to copy the test file to if it causes a crash.
# @param file The test file.
# @param type The type of test. This is just a string that would make sense
# to the user.
# @param test The test. It could be an entire file, or just one line.
# @param environ The environment to run the command under.
def run_test(cmd, exebase, tout, indata, out, file, type, test, environ=None):
try:
p = subprocess.run(cmd, timeout=tout, input=indata, stdout=subprocess.PIPE,
@ -53,6 +81,13 @@ def run_test(cmd, exebase, tout, indata, out, file, type, test, environ=None):
except subprocess.TimeoutExpired:
print("\n {} timed out. Continuing...\n".format(exebase))
# Creates and runs a test. This basically just takes a file, runs it through the
# appropriate calculator as a whole file, then runs it through the calculator
# using stdin.
# @param file The file to test.
# @param tout The timeout to use.
# @param environ The environment to run under.
def create_test(file, tout, environ=None):
print(" {}".format(file))
@ -78,6 +113,10 @@ def create_test(file, tout, environ=None):
"running {} through stdin".format(file), file, environ)
# Get the children of a directory.
# @param dir The directory to get the children of.
# @param get_files True if files should be gotten, false if directories should
# be gotten.
def get_children(dir, get_files):
dirs = []
with os.scandir(dir) as it:
@ -90,12 +129,17 @@ def get_children(dir, get_files):
return dirs
# Returns the correct executable name for the directory under test.
# @param d The directory under test.
def exe_name(d):
return "bc" if d == "bc1" or d == "bc2" or d == "bc3" else "dc"
# Housekeeping.
script = sys.argv[0]
testdir = os.path.dirname(script)
# Must run this script alone.
if __name__ != "__main__":
usage()
@ -110,6 +154,7 @@ exedir = sys.argv[idx]
asan = (exedir == "--asan")
# We could possibly run under ASan. See later for what that means.
if asan:
idx += 1
if len(sys.argv) < idx + 1:
@ -118,6 +163,7 @@ if asan:
print("exedir: {}".format(exedir))
# Grab the correct directory of AFL++ results.
if len(sys.argv) >= idx + 2:
resultsdir = sys.argv[idx + 1]
else:
@ -134,6 +180,7 @@ else:
print("resultsdir: {}".format(resultsdir))
# More command-line processing.
if len(sys.argv) >= idx + 3:
exe = sys.argv[idx + 2]
else:
@ -141,6 +188,7 @@ else:
exebase = os.path.basename(exe)
# Use the correct options.
if exebase == "bc":
halt = "halt\n"
options = "-lq"
@ -148,6 +196,7 @@ else:
halt = "q\n"
options = "-x"
# More command-line processing.
if len(sys.argv) >= idx + 4:
exe = [ exe, sys.argv[idx + 3:], options ]
else:
@ -161,6 +210,7 @@ print(os.path.realpath(os.getcwd()))
dirs = get_children(resultsdir, False)
# Set the correct ASAN_OPTIONS.
if asan:
env = os.environ.copy()
env['ASAN_OPTIONS'] = 'abort_on_error=1:allocator_may_return_null=1'
@ -171,15 +221,18 @@ for d in dirs:
print(d)
# Check the crash files.
files = get_children(d + "/crashes/", True)
for file in files:
file = d + "/crashes/" + file
create_test(file, timeout)
# If we are running under ASan, we want to check all files. Otherwise, skip.
if not asan:
continue
# Check all of the test cases found by AFL++.
files = get_children(d + "/queue/", True)
for file in files:
@ -187,4 +240,3 @@ for d in dirs:
create_test(file, timeout * 2, env)
print("Done")

@ -51,6 +51,9 @@ virtlimit=1000000
ulimit -v $virtlimit
# This script is designed to allocate lots of memory with a lot of caching of
# numbers (the function f() specifically). Then, it's designed allocate one
# large number and grow it until allocation failure (the function g()).
"$scriptdir/../bin/bc" <<*EOF
define f(i, n) {

@ -27,6 +27,7 @@
# POSSIBILITY OF SUCH DAMAGE.
#
# Print usage and exit with an error.
usage() {
printf "usage: %s install_dir exec_suffix\n" "$0" 1>&2
exit 1

@ -27,6 +27,12 @@
# POSSIBILITY OF SUCH DAMAGE.
#
# This script is NOT meant to be run! It is meant to be sourced by other
# scripts.
# Reads and follows a link until it finds a real file. This is here because the
# readlink utility is not part of the POSIX standard. Sigh...
# @param f The link to find the original file for.
readlink() {
_readlink_f="$1"
@ -51,6 +57,9 @@ readlink() {
printf '%s' "${_readlink_f##*$_readlink_d/}"
}
# Quick function for exiting with an error.
# @param 1 A message to print.
# @param 2 The exit code to use.
err_exit() {
if [ "$#" -ne 2 ]; then
@ -62,6 +71,10 @@ err_exit() {
exit "$2"
}
# Check the return code on a test and exit with a fail if it's non-zero.
# @param d The calculator under test.
# @param err The return code.
# @param name The name of the test.
checktest_retcode() {
_checktest_retcode_d="$1"
@ -79,6 +92,14 @@ checktest_retcode() {
fi
}
# Check the result of a test. First, it checks the error code using
# checktest_retcode(). Then it checks the output against the expected output
# and fails if it doesn't match.
# @param d The calculator under test.
# @param err The error code.
# @param name The name of the test.
# @param test_path The path to the test.
# @param results_name The path to the file with the expected result.
checktest() {
_checktest_d="$1"
@ -109,6 +130,11 @@ checktest() {
fi
}
# Die. With a message.
# @param d The calculator under test.
# @param msg The message to print.
# @param name The name of the test.
# @param err The return code from the test.
die() {
_die_d="$1"
@ -128,6 +154,10 @@ die() {
err_exit "$_die_str" "$_die_err"
}
# Check that a test did not crash and die if it did.
# @param d The calculator under test.
# @param error The error code.
# @param name The name of the test.
checkcrash() {
_checkcrash_d="$1"
@ -145,6 +175,12 @@ checkcrash() {
fi
}
# Check that a test had an error or crash.
# @param d The calculator under test.
# @param error The error code.
# @param name The name of the test.
# @param out The file that the test results were output to.
# @param exebase The name of the executable.
checkerrtest()
{
_checkerrtest_d="$1"
@ -191,6 +227,16 @@ checkerrtest()
fi
}
# Replace a substring in a string with another. This function is the *real*
# workhorse behind configure.sh's generation of a Makefile.
#
# This function uses a sed call that uses exclamation points `!` as delimiters.
# As a result, needle can never contain an exclamation point. Oh well.
#
# @param str The string that will have any of the needle replaced by
# replacement.
# @param needle The needle to replace in str with replacement.
# @param replacement The replacement for needle in str.
substring_replace() {
_substring_replace_str="$1"
@ -208,6 +254,13 @@ substring_replace() {
printf '%s' "$_substring_replace_result"
}
# Generates an NLS path based on the locale and executable name.
#
# This is a monstrosity for a reason.
#
# @param nlspath The $NLSPATH
# @param locale The locale.
# @param execname The name of the executable.
gen_nlspath() {
_gen_nlspath_nlspath="$1"
@ -219,22 +272,27 @@ gen_nlspath() {
_gen_nlspath_execname="$1"
shift
# Split the locale into its modifier and other parts.
_gen_nlspath_char="@"
_gen_nlspath_modifier="${_gen_nlspath_locale#*$_gen_nlspath_char}"
_gen_nlspath_tmplocale="${_gen_nlspath_locale%%$_gen_nlspath_char*}"
# Split the locale into charset and other parts.
_gen_nlspath_char="."
_gen_nlspath_charset="${_gen_nlspath_tmplocale#*$_gen_nlspath_char}"
_gen_nlspath_tmplocale="${_gen_nlspath_tmplocale%%$_gen_nlspath_char*}"
# Check for an empty charset.
if [ "$_gen_nlspath_charset" = "$_gen_nlspath_tmplocale" ]; then
_gen_nlspath_charset=""
fi
# Split the locale into territory and language.
_gen_nlspath_char="_"
_gen_nlspath_territory="${_gen_nlspath_tmplocale#*$_gen_nlspath_char}"
_gen_nlspath_language="${_gen_nlspath_tmplocale%%$_gen_nlspath_char*}"
# Check for empty territory and language.
if [ "$_gen_nlspath_territory" = "$_gen_nlspath_tmplocale" ]; then
_gen_nlspath_territory=""
fi
@ -243,6 +301,8 @@ gen_nlspath() {
_gen_nlspath_language=""
fi
# Prepare to replace the format specifiers. This is done by wrapping the in
# pipe characters. It just makes it easier to split them later.
_gen_nlspath_needles="%%:%L:%N:%l:%t:%c"
_gen_nlspath_needles=$(printf '%s' "$_gen_nlspath_needles" | tr ':' '\n')
@ -251,6 +311,7 @@ gen_nlspath() {
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "$_gen_nlspath_i" "|$_gen_nlspath_i|")
done
# Replace all the format specifiers.
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%%" "%")
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%L" "$_gen_nlspath_locale")
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%N" "$_gen_nlspath_execname")
@ -258,7 +319,9 @@ gen_nlspath() {
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%t" "$_gen_nlspath_territory")
_gen_nlspath_nlspath=$(substring_replace "$_gen_nlspath_nlspath" "%c" "$_gen_nlspath_charset")
# Get rid of pipe characters.
_gen_nlspath_nlspath=$(printf '%s' "$_gen_nlspath_nlspath" | tr -d '|')
# Return the result.
printf '%s' "$_gen_nlspath_nlspath"
}

@ -27,21 +27,55 @@
# POSSIBILITY OF SUCH DAMAGE.
#
# Just print the usage and exit with an error.
usage() {
printf 'usage: %s [-a] [afl_compiler]\n' "$0" 1>&2
printf '\n'
printf ' If -a is given, then an ASan ready build is created.\n'
printf ' Otherwise, a normal fuzz build is created.\n'
printf ' The ASan-ready build is for running under\n'
printf ' `tests/afl.py --asan`, which checks that there were no\n'
printf ' memory errors in any path found by the fuzzer.\n'
printf ' It might also be useful to run scripts/randmath.py on an\n'
printf ' ASan-ready binary.\n'
exit 1
}
script="$0"
scriptdir=$(dirname "$script")
asan=0
# Process command-line arguments.
while getopts "a" opt; do
case "$opt" in
a) asan=1 ; shift ;;
?) usage "Invalid option: $opt" ;;
esac
done
if [ $# -lt 1 ]; then
CC=afl-clang-lto
else
CC="$1"
fi
# We want this for extra sensitive crashing
AFL_HARDEN=1
cd "$scriptdir/.."
set -e
CC="$CC" CFLAGS="-flto" ./configure.sh -gO3 -z
if [ "$asan" -ne 0 ]; then
CFLAGS="-flto -fsanitize=address"
else
CFLAGS="-flto"
fi
# We want a debug build because asserts are counted as crashes too.
CC="$CC" CFLAGS="$CFLAGS" ./configure.sh -gO3 -z
make -j16

@ -184,7 +184,7 @@ while getopts "l" opt; do
case "$opt" in
l) all_locales=1 ; shift ;;
?) usage "Invalid option $opt" ;;
?) usage "Invalid option: $opt" ;;
esac
done

@ -27,79 +27,118 @@
# POSSIBILITY OF SUCH DAMAGE.
#
# Print the usage and exit with an error.
usage() {
printf "usage: %s manpage\n" "$0" 1>&2
exit 1
}
print_manpage() {
# Generate a manpage and print it to a file.
# @param md The markdown manual to generate a manpage for.
# @param out The file to print the manpage to.
gen_manpage() {
_print_manpage_md="$1"
_gen_manpage_md="$1"
shift
_print_manpage_out="$1"
_gen_manpage_out="$1"
shift
cat "$manualsdir/header.txt" > "$_print_manpage_out"
cat "$manualsdir/header_${manpage}.txt" >> "$_print_manpage_out"
pandoc -f commonmark_x -t man "$_print_manpage_md" >> "$_print_manpage_out"
cat "$manualsdir/header.txt" > "$_gen_manpage_out"
cat "$manualsdir/header_${manpage}.txt" >> "$_gen_manpage_out"
pandoc -f commonmark_x -t man "$_gen_manpage_md" >> "$_gen_manpage_out"
}
gen_manpage() {
# Generate a manual from a template and print it to a file before generating
# its manpage.
# param args The type of markdown manual to generate. This is a string that
# corresponds to build type (see the Build Type section of the
# manuals/build.md manual).
gen_manual() {
_gen_manpage_args="$1"
_gen_manual_args="$1"
shift
_gen_manpage_status="$ALL"
_gen_manpage_out="$manualsdir/$manpage/$_gen_manpage_args.1"
_gen_manpage_md="$manualsdir/$manpage/$_gen_manpage_args.1.md"
_gen_manpage_temp="$manualsdir/temp.1.md"
_gen_manpage_ifs="$IFS"
rm -rf "$_gen_manpage_out" "$_gen_manpage_md"
# Set up some local variables. $manualsdir and $manpage from from the
# variables outside the function.
_gen_manual_status="$ALL"
_gen_manual_out="$manualsdir/$manpage/$_gen_manual_args.1"
_gen_manual_md="$manualsdir/$manpage/$_gen_manual_args.1.md"
_gen_manual_temp="$manualsdir/temp.1.md"
# We need to set IFS, so we store it here for restoration later.
_gen_manual_ifs="$IFS"
# Remove the files that will be generated.
rm -rf "$_gen_manual_out" "$_gen_manual_md"
# Here is the magic. This loop reads the template line-by-line, and based on
# _gen_manual_status, either prints it to the markdown manual or not.
#
# Here is how the template is set up: it is a normal markdown file except
# that there are sections surrounded tags that look like this:
#
# {{ <build_type_list> }}
# ...
# {{ end }}
#
# Those tags mean that whatever build types are found in the
# <build_type_list> get to keep that section. Otherwise, skip.
#
# Obviously, the tag itself and its end are not printed to the markdown
# manual.
while IFS= read -r line; do
# If we have found an end, reset the status.
if [ "$line" = "{{ end }}" ]; then
if [ "$_gen_manpage_status" -eq "$ALL" ]; then
# Some error checking. This helps when editing the templates.
if [ "$_gen_manual_status" -eq "$ALL" ]; then
err_exit "{{ end }} tag without corresponding start tag" 2
fi
_gen_manpage_status="$ALL"
_gen_manual_status="$ALL"
elif [ "${line#\{\{* $_gen_manpage_args *\}\}}" != "$line" ]; then
# We have found a tag that allows our build type to use it.
elif [ "${line#\{\{* $_gen_manual_args *\}\}}" != "$line" ]; then
if [ "$_gen_manpage_status" -ne "$ALL" ]; then
# More error checking. We don't want tags nested.
if [ "$_gen_manual_status" -ne "$ALL" ]; then
err_exit "start tag nested in start tag" 3
fi
_gen_manpage_status="$NOSKIP"
_gen_manual_status="$NOSKIP"
# We have found a tag that is *not* allowed for our build type.
elif [ "${line#\{\{*\}\}}" != "$line" ]; then
if [ "$_gen_manpage_status" -ne "$ALL" ]; then
if [ "$_gen_manual_status" -ne "$ALL" ]; then
err_exit "start tag nested in start tag" 3
fi
_gen_manpage_status="$SKIP"
_gen_manual_status="$SKIP"
# This is for normal lines. If we are not skipping, print.
else
if [ "$_gen_manpage_status" -ne "$SKIP" ]; then
printf '%s\n' "$line" >> "$_gen_manpage_temp"
if [ "$_gen_manual_status" -ne "$SKIP" ]; then
printf '%s\n' "$line" >> "$_gen_manual_temp"
fi
fi
done < "$manualsdir/${manpage}.1.md.in"
uniq "$_gen_manpage_temp" "$_gen_manpage_md"
rm -rf "$_gen_manpage_temp"
# Remove multiple blank lines.
uniq "$_gen_manual_temp" "$_gen_manual_md"
# Remove the temp file.
rm -rf "$_gen_manual_temp"
IFS="$_gen_manpage_ifs"
# Reset IFS.
IFS="$_gen_manual_ifs"
print_manpage "$_gen_manpage_md" "$_gen_manpage_out"
# Generate the manpage.
gen_manpage "$_gen_manual_md" "$_gen_manual_out"
}
set -e
@ -110,11 +149,14 @@ manualsdir="$scriptdir/../manuals"
. "$scriptdir/functions.sh"
# Constants for use later. If the set of build types is changed, $ARGS must be
# updated.
ARGS="A E H N EH EN HN EHN"
ALL=0
NOSKIP=1
SKIP=2
# Process command-line arguments.
test "$#" -eq 1 || usage
manpage="$1"
@ -122,10 +164,12 @@ shift
if [ "$manpage" != "bcl" ]; then
# Generate a manual and manpage for each build type.
for a in $ARGS; do
gen_manpage "$a"
gen_manual "$a"
done
else
print_manpage "$manualsdir/${manpage}.3.md" "$manualsdir/${manpage}.3"
# For bcl, just generate the manpage.
gen_manpage "$manualsdir/${manpage}.3.md" "$manualsdir/${manpage}.3"
fi

@ -70,13 +70,14 @@ make clean_tests > /dev/null 2> /dev/null
# doing. In fact, you cannot run it again if users have already started to use
# the old version of the tag.
if git rev-parse "$version" > /dev/null 2>&1; then
git push --delete origin "$version" > /dev/null 2> /dev/null
git tag --delete "$version" > /dev/null 2> /dev/null
:
#git push --delete origin "$version" > /dev/null 2> /dev/null
#git tag --delete "$version" > /dev/null 2> /dev/null
fi
git push > /dev/null 2> /dev/null
git tg "$version" -m "$tag_msg" > /dev/null 2> /dev/null
git push --tags > /dev/null 2> /dev/null
#git push > /dev/null 2> /dev/null
#git tg "$version" -m "$tag_msg" > /dev/null 2> /dev/null
#git push --tags > /dev/null 2> /dev/null
# This line grabs the names of all of the files in .gitignore that still exist.
ignores=$(git check-ignore * **/*)
@ -109,15 +110,18 @@ manuals/header_bc.txt
manuals/header_dc.txt
manuals/header.txt
manuals/release.md
scripts/afl.py
scripts/alloc.sh
scripts/fuzz_prep.sh
scripts/manpage.sh
scripts/package.sh
scripts/radamsa.sh
scripts/radamsa.txt
scripts/randmath.py
scripts/release_settings.txt
scripts/release.sh
tests/afl.py
tests/alloc.sh
tests/radamsa.sh
tests/radamsa.txt
tests/randmath.py
scripts/test_settings.sh
scripts/test_settings.txt
tests/bc/scripts/timeconst.bc
*EOF
)

@ -27,30 +27,43 @@
# POSSIBILITY OF SUCH DAMAGE.
#
# This script uses some non-POSIX behavior, but since it's meant for bc
# maintainers only, I can accept that.
# Get an entry from the file. If an argument exists, it is an index. Get that
# line. Otherwise, get a random line.
getentry() {
# Figure out if we get a specific or random line.
if [ $# -gt 0 ]; then
entnum="$1"
else
entnum=0
fi
# Get data from stdin and figure out how many lines there are.
e=$(cat -)
num=$(printf '%s\n' "$e" | wc -l)
# Figure out what line we are going to get. Uses bc's own PRNG.
if [ "$entnum" -eq 0 ]; then
rand=$(printf 'irand(%s) + 1\n' "$num" | "$bcdir/bc")
else
rand="$entnum"
fi
# Get the line.
ent=$(printf '%s\n' "$e" | tail -n +$rand | head -n 1)
printf '%s\n' "$ent"
}
script="$0"
dir=$(dirname "$script")
. "$dir/functions.sh"
# Command-line processing.
if [ "$#" -lt 1 ]; then
printf 'usage: %s dir\n' "$0"
exit 1
@ -59,23 +72,20 @@ fi
d="$1"
shift
dir=$(dirname "$script")
. "$dir/../scripts/functions.sh"
bcdir="$dir/../bin"
# Figure out the correct input directory.
if [ "$d" = "bc" ]; then
inputs="$dir/../../inputs"
inputs="$dir/../tests/fuzzing/bc_inputs1"
opts="-lq"
elif [ "$d" = "dc" ]; then
inputs="$dir/../../inputs_dc"
inputs="$dir/../test/fuzzing/dc_inputs"
opts="-x"
else
err_exit "wrong type of executable" 1
fi
export ASAN_OPTIONS="abort_on_error=1"
export ASAN_OPTIONS="abort_on_error=1:allocator_may_return_null=1"
entries=$(cat "$dir/radamsa.txt")
@ -83,8 +93,10 @@ IFS=$'\n'
go=1
# Infinite loop.
while [ "$go" -ne 0 ]; do
# If we are running bc, fuzz command-line arguments in BC_ENV_ARGS.
if [ "$d" = "bc" ]; then
entry=$(cat -- "$dir/radamsa.txt" | getentry)
@ -109,6 +121,7 @@ while [ "$go" -ne 0 ]; do
l=$(cat "$inputs/$f" | wc -l)
ll=$(printf '%s^2\n' "$l" | bc)
# Fuzz on the AFL++ inputs.
for i in $(seq 1 2); do
data=$(cat "$inputs/$f" | radamsa -n 1)
printf '%s\n' "$data" > "$dir/../.log_${d}_test.txt"

@ -32,42 +32,89 @@ import random
import sys
import subprocess
# I want line length to *not* affect differences between the two, so I set it
# as high as possible.
env = {
"BC_LINE_LENGTH": "65535",
"DC_LINE_LENGTH": "65535"
}
# Generate a random integer between 0 and 2^limit.
# @param limit The power of two for the upper limit.
def gen(limit=4):
return random.randint(0, 2 ** (8 * limit))
# Returns a random boolean for whether a number should be negative or not.
def negative():
return random.randint(0, 1) == 1
# Returns a random boolean for whether a number should be 0 or not. I decided to
# have it be 0 every 2^4 times since sometimes it is used to make a number less
# than 1.
def zero():
return random.randint(0, 2 ** (8) - 1) == 0
return random.randint(0, 2 ** (4) - 1) == 0
# Generate a real portion of a number.
def gen_real():
# Figure out if we should have a real portion. If so generate it.
if negative():
n = str(gen(25))
length = gen(7 / 8)
if len(n) < length:
n = ("0" * (length - len(n))) + n
else:
n = "0"
return n
# Generates a number (as a string) based on the parameters.
# @param op The operation under test.
# @param neg Whether the number can be negative.
# @param real Whether the number can be a non-integer.
# @param z Whether the number can be zero.
# @param limit The power of 2 upper limit for the number.
def num(op, neg, real, z, limit=4):
# Handle zero first.
if z:
z = zero()
else:
z = False
if z:
return 0
# Generate a real portion maybe
if real:
n = gen_real()
if n != "0":
return "0." + n
return "0"
# Figure out if we should be negative.
if neg:
neg = negative()
# Generate the integer portion.
g = gen(limit)
if real and negative():
n = str(gen(25))
length = gen(7 / 8)
if len(n) < length:
n = ("0" * (length - len(n))) + n
# Figure out if we should have a real number. negative() is used to give a
# 50/50 chance of getting a negative number.
if real:
n = gen_real<