Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add random functions #1260

Closed
wants to merge 2 commits into from
Closed

Add random functions #1260

wants to merge 2 commits into from

Conversation

erikbrinkman
Copy link
Contributor

  • Functions include: rand, randint, shuffle, rand_select, and
    rand_select_rep.
  • These are not generated securely (e.g. with "stdlib.h"'s rand and
    srand), however they should be good enough for most purposes.

fixes #677 and fixes #1038

@coveralls
Copy link

Coverage Status

Coverage increased (+0.2%) to 85.519% when pulling 0754bc3 on erikbrinkman:rand into 0b82185 on stedolan:master.

@nicowilliams
Copy link
Contributor

Hi. Thanks for this contribution. It's very nice. What I'd like though is to use /dev/urandom (or getrandom() or getentropy() on Unix, and CNG on Windows. rand() is too weak. Of course, /dev/urandom is a... slow, by comparison to an in-process PRNG seeded by /dev/urandom, but it'd be a start. I'd accept a contribution that uses /dev/urandom on Unix and rand() on Windows.

@erikbrinkman
Copy link
Contributor Author

This change is implemented, it will need to be repushed when appveyor builds are fixed to make sure it works on windows.

A number of questions / information about the patch:

  • Currently the way this works is it tries to use the getrandom syscall if it exists. This call still works in processes that don't device access, making it better than /dev/urandom (as far I can tell the linux ci builds don't have this feature, but it works in my environemnt, and the autoconf test seems accurate.
  • As a fall back it uses /dev/urandom
  • On windows or any device without /dev/urandom, this seeds a pseudorandom number generator with time(NULL)

I also wanted to be able to specify a random seed to get deterministic random output. Since rand() has so many issues, I added a BSD Licensed version of Mersenne Twister for pseudorandom generation. On systems without getrandom or /dev/urandom, time seeds this generator (as stated above). This implementation of MT seems like the appropriate one to use, but it is BSD and so requires the license be distributed with binaries. Is that a feasible requirement for jq? Are there changes I can make to the repository so that binary builds for releases always include the license, or is this enough of a problem that I should reimplement MT or some other PRNG myself? I'm generally against anything like this, as small errors in PRNGs can lead to horrible performance.

@nicowilliams
Copy link
Contributor

I guess we could add a --copyright option to jq... bummer, though I think that's OK.

@nicowilliams
Copy link
Contributor

BTW, there's more liberally licensed code in an IETF RFC for the various SHA functions. We could just use HMAC-SHA256 with a key generated from /dev/urandom, first applied to a constant, outputting half the HMAC output as a the output and using the other half as the input to the next iteration.

Also, the Mersenne Twister, or any LFSR, could be used as a non-cryptographically-secure PRNG, because that will be faster than an HMAC-based one.

I can probably take over from here next week, maybe, if you don't want to put up with all my requests! :)

@erikbrinkman
Copy link
Contributor Author

I'm okay putting up with these requests, they're not hard. I guess before I update this, it's reasonable to discuss exactly what interfaces we/you want, so I don't waste any time. For the record, I'm going to be talking about *nix systems, I don't really care about windows...

  1. Draw all bits from getrandom(GRND_RANDOM) or /dev/random
  2. Draw all bits from getrandom() or /dev/urandom
  3. Seed PRNG (either crypto, or LFSR) with 1 or 2 (e.g. the cartesian product of each option)
  4. Seed PRNG (either crypto, or LFSR) with user provided seed.

In my mind, the only ones that seem unnecessary are

  • LFSR seeded with /dev/random
  • Maybe all bits from /dev/urandom as that's basically the same as seeding LFSR from /dev/urandom or /dev/random

The next question would be what the cli would look like. I could see something somewhat like the --rand-seed <seed> option that allows specifying a seed, and a --security flag (maybe with a short option) where specifying it once moves from LFSR to crypo, two moves the seed to /dev/random, and three specified that all randomness is from /dev/random. Thoughts / other ideas?

As far as the license goes, it seems like we probably just need to have a license file available whenever anyone gets the binary, e.g. a download option on the website, and a licenses folder for things like ubuntu or otherwise where the binary is just distributed. The authors asked to get an email if it was ever used, so I can also ask if they have a specific request of how thy want the license to be available.

@nicowilliams
Copy link
Contributor

@erikbrinkman

Just (2) and (3). No /dev/random, only urandom, and no user-provided seeds. If you want to provide an env var for user seeding, that's fine with me.

I see about the license -- that's fine.

I'd also provide a saferandom and an unsaferandom (say; I don't really care about the names very much, as long as there's a distinction). The latter being an LFSR/whatever, and the former being cryptographically-safe. These could output numbers. If the user wants strings... we could have a utility to make strings from numbers.

@erikbrinkman
Copy link
Contributor Author

  1. We seem to have significantly different views about how this should work. In the way it's currently implemented, there are a number of random primitives that I figured would be most useful:

    • rand: which returns a double in [0, 1)
    • randint: which returns a random integer in [0, b)
    • shuffle: randomly permute an array

    All of these can obviously be written just with the rand primitive (a little less efficiently), but more importantly they all seem useful without having to reimplement i.e. randint(a; b) vs rand * (b - a) | floor + a. The latter is valid but significantly less intuitive, especially when doing something like ./jq -s '.[randint(length)]'.

    One way to implement the different PRNGs, as you suggested, would be to provide two different random interfaces, e.g. saferandom and unsaferandom but this seems very verbose, especially for all of the primitives that would arguably be convenient. On the plus side, it would allow for the same program to use both "safe" and "unsafe" randomness. The other alternative, which is what I was thinking, would have a command line flag to switch from using LFSR to something cryptographically safe (or vice versa). This allows keeping a simple set of random functions, but would require all randomness to be "slow" if you want any of it to be cryptographically secure.

  2. For the sake of not generating random bits, it's nice to have integral types with fixed size, i.e. uint32_t, however this requires use of headers that aren't necessarily available on all systems "stdint.h". I know the configure script checks for this header, but I'm not accustomed to writing platform independent c. Is there an appropriate way to handle the fact that these types are missing? Something maybe like:

    #if ! HAVE_STDINT
    #define uint32_t unsigned long
    #endif
    
  3. Is there a reason why you prefer a random seed environment variable instead of a command line flag?

Thanks for the help / advice / putting up with all of these questions.

@erikbrinkman
Copy link
Contributor Author

On a side note: Makoto Matsumoto was very prompt, and said:

Hosting a copy on the website suffices.

So I'll make sure an update to the website is included in the PR that lists the license.

@nicowilliams
Copy link
Contributor

I've no objection to the builtin functions you propose.

@erikbrinkman
Copy link
Contributor Author

I tried but couldn't find a cryptographically secure implementation of a DRBG that I thought was good enough to just use. The IETF does have nice implementations of HMAC-SHA* but NIST's recommendations on how to turn that into a DRBG are relatively complicated and I'd rather follow the first rule of crypto, "don't implement your own crypto", even if I have an implementation of HMAC-SHA256.

If you (or anyone else who reads this) wants to try to implement something, the following script should download the IETF code in a way that the tests will compile and run. It seems like their license is also close to BSD, so I would add a copyright notice to the download page, like I did for the TinyMT implementation.

curl https://tools.ietf.org/rfc/rfc6234.txt | sed -E '/Eastlake & Hansen +Informational +\[Page [0-9]+\]/{N;N;d}' | tr -s '\n' | gawk 'BEGIN { FILE = "/dev/null" } { if (match($0, /\/\*+ ([-a-z0-9]+\.[ch]) \*+\/$/, M)) { FILE = M[1] } else if (match($0, /^([0-9]+\.)+/)) { FILE = "/dev/null" } print $0 > FILE }'

Other things that you may have an opinion about in this future change:

  • I moved the MT implementation to a submodule from their repository. Testing that it works and everything is updated appropriately is taking a little bit of time.
  • In an effort to support switching the type of DRBG, I had to change the way random command line arguments were parsed. To make it easier to maintain all of the bit flags I switched the command line args from being an enumeration with bitmasks to be a bitfield struct. I think this is more readable, but it's probably a stylistic issue that you may not agree with.

src/rand.c Outdated
void jq_rand_init() {
uint64_t seed;
FILE *devur = fopen("/dev/urandom", "r");
if (fread(&seed, sizeof(seed), 1, devur) != 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to check that devur isn't NULL... That should raise an error.

Ideally we could #ifdef out on WIN32 so that on Windows these builtins elicit a compile-time error.

We should also have a notion of unsupported builtins so that a compile-time error about them can be more user-friendly.

And, lastly, WIN32 support, using CNG, would be nice.

Lastly, getentropy(2) or getrandom(2) might be better than reading from /dev/urandom where those are supported.

To be clear, I'm not asking for Windows support though, or for support for getentropy(2)/getrandom(2).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does actually check at compile time for the existence of the "getrandom" syscall, and the "/dev/urandom" file. It's still possible for "dev/urandom" to be null, so you're right, it should be checked.

src/main.c Outdated
@@ -528,6 +549,13 @@ int main(int argc, char* argv[]) {
if (getenv("JQ_COLORS") != NULL && !jq_set_colors(getenv("JQ_COLORS")))
fprintf(stderr, "Failed to set $JQ_COLORS\n");

// Randomness
if (options & USER_DEFINED_SEED) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we want user-defined seeds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why I added in a user defined seed is for reproducability. Often time I need a source of numbers that seem random, but I also want to guarantee the the result is deterministic. That's why I added this option.

src/rand.c Outdated
}

double jq_rand_double() {
return tinymt64_generate_double01(&fast_rand);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why wouldn't we always read from /dev/urandom (or getentropy(2) or getrandom(2) or CNG)?

I'm not keen on having our own PRNG. That way lies trouble.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, what's tinymt64_generate_double01()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I've mentioned previously, my motivation isn't cryptographic security, but often reproducability, which is why I used a PRNG and allowed specifying a seed. I agree that out own PRNG spell trouble, which is why I'm using code from the authors of Mersenne Twister as a submodule. tinymy64_generate_double01 is the name of their function that returns the next double in [0, 1).

@nicowilliams
Copy link
Contributor

Let's not add any PRNGs that are not cryptographically secure.

Better yet, let's not add PRNGs.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.2%) to 84.936% when pulling d157bc5 on erikbrinkman:rand into 476b367 on stedolan:master.

@nicowilliams
Copy link
Contributor

What I meant is that reading from /dev/urandom is good enough, and it should be cryptographically secure, and we wouldn't need any PRNGs. It might not be as performant as an in-process PRNG, but we can always add an in-process PRNG later. How's that sound?

@erikbrinkman
Copy link
Contributor Author

Ultimately, I'm just aiming to be a contributor, and I'll respect whatever decisions you make. I would like the ability to specify a seed so that I can have reproducable randomness. A potential compromise would be to constantly pull from "getrandom" or "/dev/urandom" if no seed is specified, and only seed a PRNG if a seed is specified.

The other decision to be made is how to handle when /dev/urandom and getrandom don't exist. Do you want to seed a PRNG with time like I'm doing now, or compile out the random functions entirely?

@nicowilliams
Copy link
Contributor

I like that compromise. It might be good to have a differently named set of builtins that always use /dev/urandom or similar.

@erikbrinkman
Copy link
Contributor Author

I considered that, but as is, this adds five new functions. I can't think of any reason why you'd want both secure random and PRNG generation in the same script, so having it behind the flag of --random-seed seems like a cleaner way of providing the distinction than having ten new functions where some are prefixed / suffixed to decide between computer random and PRNG. However, the call is yours, which do you prefer? Also, do you have thoughts on how windows should be handled in this PR, i.e. no random functions or PRNG?

@nicowilliams
Copy link
Contributor

As to Windows, for now just return a run-time error.

As to a --random-seed, i'd rather not have additional command-line arguments if we don't need them.

@nicowilliams
Copy link
Contributor

And thanks!

@erikbrinkman
Copy link
Contributor Author

I understand the desire to minimize command line args, but without the ability to set the seed, there's no way to get the reproducability from the PRNG. I could add a function to set the seed, but I can't think of an ideal way to do so. One potential implementation would take an argument to set the seed to and otherwise leave the input unchanged so you could do something like jq 'set_seed(1234) | ...', but this wouldn't necessarily work as intended. Imagine your input is a stream of lists and you want to shuffle all of them, it wouldn't be possible to get different shuffles, because each time it'd reset the seed and shuffle the same way again. More concretely imagine your input looked like:

[1, 2, 3]
[1, 2, 3]
[1, 2, 3]

And you put it through jq 'set_seed(0) | shuffle', you might get an output like

[3, 1, 2]
[3, 1, 2]
[3, 1, 2]

I'm open to suggestions if you can think of a better way to set the seed in the face of input streams and/or the executive decision that this isn't a use case to support. If you still deem this unnecessary and would just prefer I implement taking all randomness from system sources I can do that too.

P.S. I'm doing this because I like jq, so thank you for maintaining it!

@pkoppstein
Copy link
Contributor

As to a --random-seed, i'd rather not have additional command-line arguments if we don't need them.

It should definitely be possible for users to set the seed in a straightforward way. Personally, I think a command-line flag would be fine, but there are two alternatives that would be possible: something based on $ARGS.named, and something based on env. Between these, the former would be far better, e.g.

 jq --argjson seed 12332 

@nicowilliams
Copy link
Contributor

@erikbrinkman

Well, the idea was to use jq -n 'set_seed(...) | inputs | ...' when you want to set a seed. I'd be OK with a --seed option, but the default should be "random seed".

As to @pkoppstein's idea of using a --arg for passing in a seed, the argument name should be a non-ident-like one, that way it can't conflict with users' choices of variables, so --argjson .seed 1234, for example, would work.

@erikbrinkman
Copy link
Contributor Author

I like that idea. I'll look into how to catch setting that variable. Thanks for the feedback!

- Functions include: `rand`, `randint`, `shuffle`, `rand_select`, and
  `rand_select_rep`.
- By default these will pull from system random sources (/dev/urandom or
  getrandom) if they exist. Optionally a Mersenne Twister PRNG can be
  seeded with `--arg .seed <seed>` or `--argjson .seed <seed>`. This
  also allows using pseudoradom numbers on machines without hardware
  randomness.
  exists to add new PRNGs.
- The build and test of TinyMT was added nonrecursively to the existing
  automake file, due to a) not needing the entire library, and b) the
  library not supporting a standard `check` target.

fixes #677 and fixes #1038
@erikbrinkman
Copy link
Contributor Author

Everything should be fixed. By default random functions will pull from the system random source, but by specifying a seed with either --arg .seed 123 or --argjson .seed 123 they will switch to using the MT PRNG. What this means is that on windows using random functions without specifying a seed causes an error.

A downside of this is that the random jq tests and manual tests will fail on windows since no seed is specified. If you want, I could fix this by specifying a seed when running those tests, and adding a few shtests that don't use the seed with a guard around windows. However, it looks like test failures are ignored on windows builds as it is / the random tests aren't the only ones that fail. Maybe these failing tests will inspire a windows developer to add CNG support?

@pkoppstein
Copy link
Contributor

This is to confirm that I had no problems installing PR #1260 on a "High Sierra" Mac:

./jqtest
325 of 325 tests passed (0 malformed)

The size of jq increased from 355872 to 362152.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.4%) to 85.029% when pulling 01a7d69 on erikbrinkman:rand into 79ece35 on stedolan:master.

@pkoppstein
Copy link
Contributor

Being able to set the seed at the command-line is great, but having it does not preclude being able to set the seed programmatically as well. However, if making further changes in this PR is going to delay the next official release of jq, I'd vote for no delay.

Regarding the details: what would set_seed(n) emit? Its input? Would it be more in keeping with other builtins (e.g. not) to make it a 0-arity filter?

@erikbrinkman
Copy link
Contributor Author

I'm happy to add a set seed, and could have the modified PR in tonight. My feeling would be to provide both variants. A 0-arity that sets the seed to its input and returns the passed in seed, as well as a one argument version that otherwise works as the identity presuming the input is valid. As you mentioned, the 0-airty fits more in line with builtins, but with the 1-arity one could do things like:

jq -n '[range(4)] | (set_seed(0) | shuffle) == (set_seed(0) | shuffle)'

Not that that example is particularly useful, but it seems like seeds will mostly come externally and it seems bad to force users to otherwise do

jq -n '[range(4)] | . as $inp | 0 | set_seed | $inp | shuffle'

@nicowilliams
Copy link
Contributor

I'll review this later this week.

We might not accept it into master until 1.6 is released. Alternatively we might create a branch for 1.6 (we have other things we want to push to master but not 1.6, so it's probably time to start using release branches).

@pkoppstein
Copy link
Contributor

A set_seed that merely returned its input would be pretty pointless, I think; one that set the seed based on nowwould be handy, but I suspect minimalist impulses will come to bear here ...

@nicowilliams - @erikbrinkman has evidently put much thought and care into this contribution, which I believe is important and deserves recognition, so I hope it makes it into 1.6 for both reasons.

@erikbrinkman
Copy link
Contributor Author

I see the purpose of set_seed returning it's input to be nondestructive, so one could do

jq 'set_seed(now * 1000 | floor) | ...' input.json

and it would work, instead of having to do something like:

jq -n 'now * 1000 | floor | set_seed | inputs | ...' input.json

However, setting the seed off of the time is probably only useful on windows right now as omitting it almost certainly gives you a better source of randomness. Ultimately, I'm kind of on the fence. Any of these functions are possible, but I'm not sure how necessary they are, or what primary usecase there is. if you just want to set the seed off of the time, then you can always do

jq --arg .seed "$(date +'%s')" ...

@pkoppstein
Copy link
Contributor

@erikbrinkman - Maybe I need to be a bit clearer about a couple of things.

First, I'd really like the randomness functions that you've already implemented (and documented) so well to be part of the next official jq release, and for that release, I'm fine with the functionality that is in place now (i.e. rand and friends, and the ".seed" method for setting the seed).

Second, I believe that being able to set the seed programmatically (in addition to the command-line method) would be a Very Good Thing. set_seed(n) fills that need satisfactorily, and it makes sense for it to emit its input. However, I'm not wedded to the name set_seed. Indeed, since the parameter name .seed is already in effect, the name seed for the function has much to recommend it. For the remainder of this post, though, I'll stick with the name "set_seed" and assume that jq supports both .seed and the functionality of set_seed/1.

Third, it seems to me that having set_seed/0 with the semantics of:

def set_seed:  set_seed(.);

would probably be worse than useless, for the reason you already gave (i.e., to use it, one would have to use the long-winded idiom: . as $in | 0 | set_seed | $in |

On the other hand, with set_seed/1 in place, a definition such as set_seed(now|floor) would provide a small convenience, which is what jq is all (or at least mostly) about :-)

@erikbrinkman
Copy link
Contributor Author

That all makes sense to me. I added a commit that adds the function seed/1 that sets the seed. I kept it as a separate commit in case you don't want a seed function right now, or are against the other changes I made which I describe next.

The discussion of what's the most "jq" in style got me thinking about how integers are handled since jq and json don't have an integer concept. What I decided was that the random functions should more closely match how related functions handle inputs that should ostensibly be integers. I changed randint to rand_range which will now return random elements from the stream created by range. Now rand_range produces the same distribution as [range(...)] | shuffle | .[0]. To keep this consistency, I also added rand_range/3. In addition, rand_select(n) used to error if n wasn't an integer, but now will return the same as shuffle | .[:n] which essentially takes the ceiling of n.

However, I decided that setting the seed should enforce that it's input matches it's uint representation. Otherwise a user might call seed(0.1) and seed(0.8) and expect to be setting different seeds, when they're really just calling seed(0).

Copy link
Contributor

@pkoppstein pkoppstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uniary should be unary

@erikbrinkman
Copy link
Contributor Author

@pkoppstein fixed...

@coveralls
Copy link

Coverage Status

Coverage increased (+0.5%) to 85.152% when pulling 277d9ec on erikbrinkman:rand into 79ece35 on stedolan:master.

@pkoppstein
Copy link
Contributor

All very excellent, though I think it would be better for rand_range to behave even more like range, e.g. range(0;0) emits the empty stream rather than raising an error.

@erikbrinkman
Copy link
Contributor Author

erikbrinkman commented Dec 6, 2017

I think arguably it should return null instead of empty, as one would expect rand_range(...) to produce an identical distribution to [range(...)] | shuffle | .[0]. Indexing a nonexistent element produces a null, but still a single element, not a stream.

@pkoppstein
Copy link
Contributor

@erikbrinkman - I have no strenuous objections to defining the functional semantics of rand_range(...) as [range(...)] | shuffle | .[0] but I am looking at lines such as:

if upper - lower <= 0
  then error("can't select an element from an empty range (\(lower); \(upper))")

@erikbrinkman
Copy link
Contributor Author

So if we define the semantics as it should return the same result as [range(...)] | shuffle | .[0], then those line should change to:

if upper - lower <= 0
then null

- Also rename randint to rand_range and make treatment of floating
  points match the way other jq functions handle them.
@pkoppstein
Copy link
Contributor

pkoppstein commented Dec 6, 2017

@erikbrinkman - Yes, but I believe def rand_range(lower; upper; step) will also need tweaking.

By the way, if you could easily provide some guidance regarding git, I'd appreciate it. When I tried fetching the latest version of your work, git complains:

 fatal: Refusing to fetch into current branch refs/heads/random of non-bare repository

Previously, I had successfully run: git fetch origin pull/1260/head:random

Thanks.

@erikbrinkman
Copy link
Contributor Author

erikbrinkman commented Dec 6, 2017

In the current PR, rand_range does work correctly as jq -n '[range(10000) | rand_range(0; -3; -1)] | unique' produces [-2, -1, 0].

As for git, it looks like this error could be because you're on branch random, and it could be related to my commit amending. If you're on branch random and don't have any changes you need to keep, you should be able to do git reset --hard pull/1260/head to get the latest version.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.9%) to 85.518% when pulling b128f10 on erikbrinkman:rand into 79ece35 on stedolan:master.

@pkoppstein
Copy link
Contributor

$ git status
On branch random
# ...
$ git reset --hard pull/1260/head 
fatal: ambiguous argument 'pull/1260/head': unknown revision or path not in the working tree.

I tried various other things, all leading nowhere but frustration.

@erikbrinkman
Copy link
Contributor Author

erikbrinkman commented Dec 6, 2017

@pkoppstein Actually, I think it should be git reset --hard origin/pull/1260/head. I don't have access to the PRs so I can't verify, but that should work.

@pkoppstein
Copy link
Contributor

$ git reset --hard origin/pull/1260/head
fatal: ambiguous argument 'origin/pull/1260/head': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'

@erikbrinkman
Copy link
Contributor Author

@pkoppstein What if you delete the branch and refetch it?

git checkout master
git branch -D random
git fetch origin pull/1260/head:random

@pkoppstein
Copy link
Contributor

@erikbrinkman - Thanks, that did it, though there was an ominous-looking warning:

warning: unable to rmdir modules/TinyMT: Directory not empty

@erikbrinkman
Copy link
Contributor Author

@pkoppstein That's fine. There are build artifacts left in the TinyMT (the PRNG I chose) submodule so git couldn't delete it. Since you presumably checked random out again it doesn't matter, but you can also just delete the folder yourself.

@pkoppstein
Copy link
Contributor

That's fine.
Yep, I figured as much. git, on the other hand ....

I'm also happy to report (on a High Sierra Mac):

336 of 336 tests passed (0 malformed)

If you're interested and have the time, it would be helpful if you could look at
#1543

One of the mysteries here is that I am no longer able to recompile the prior versions that would help identify the commit that evidently introduced the problem.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.5%) to 85.12% when pulling 7a4bf21 on erikbrinkman:rand into 79ece35 on stedolan:master.

return type_error2(input, j_num, "can't select a non-number of elements");
}
double length = jv_array_length(jv_copy(input));
double num = ceil(jv_number_value(jv_copy(j_num)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And - here goes the leak.
@wtlangford exactly my point about ambiguous method naming. jv_number_value doesn't consume the parameter, jv_copy here is unbalanced. Not visible until the changes where numbers become ref counted, so a kind of time bomb

@danmou
Copy link

danmou commented Jan 27, 2020

Any update on this feature? Is anything blocking it?

@nicowilliams
Copy link
Contributor

This should be superseded by #1843, which adds random and much more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Randomly select n items from array Is it possible to add a random selector?
6 participants