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

topdown/strings: make strings.replace_n use builtins.ObjectOperand #2825

Merged

Conversation

srenatus
Copy link
Contributor

@srenatus srenatus commented Oct 27, 2020

Fixes #2822.

A quick check with the old and new implementation shows:

$ for i in $(seq 0 1000); do ./opa_master eval --format=pretty 'strings.replace_n({"f":"x", "foo": "xxx"}, "foobar")' | grep -v xoobar; done | wc -l
     127
$ for i in $(seq 0 1000); do ./opa_wip eval --format=pretty 'strings.replace_n({"f":"x", "foo": "xxx"}, "foobar")' | grep -v xoobar; done | wc -l
       0

So the non-determinism should be dealt with.

Still planning to add test cases to pin down the current behaviour. ✔️

- data: {}
modules:
- |
package generated
Copy link
Contributor Author

Choose a reason for hiding this comment

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

💭 What's the standard for test cases? "generated" isn't true anymore here...

Copy link
Member

Choose a reason for hiding this comment

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

I've been using package test in most places. We might be able to just migrate all of the existing test cases to use that. Here's the hello-world example that I've pointed people at: https://github.com/open-policy-agent/opa/blob/master/test/cases/testdata/helloworld/test-helloworld-1.yaml

@srenatus srenatus force-pushed the sr/fix-2822/strings.replace_n branch 2 times, most recently from b514dea to 9d7d793 Compare October 27, 2020 12:49
@srenatus srenatus marked this pull request as ready for review October 27, 2020 12:56
- data: {}
modules:
- |
package generated
Copy link
Member

Choose a reason for hiding this comment

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

I've been using package test in most places. We might be able to just migrate all of the existing test cases to use that. Here's the hello-world example that I've pointed people at: https://github.com/open-policy-agent/opa/blob/master/test/cases/testdata/helloworld/test-helloworld-1.yaml


s, err := builtins.StringOperand(b, 2)
if err != nil {
return nil, err
}

var oldnewArr []string
for k, v := range oldnewObj {
strVal, ok := v.(string)
err = oldnewObj.Iter(func(k *ast.Term, v *ast.Term) error {
Copy link
Member

Choose a reason for hiding this comment

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

The object implementation iterates in insertion order so there could still be different outputs with this change. What about getting the keys from the object, sorting them, and then iterating over the sorted key set?

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 in strings.replace_n(o1, s) could differ from strings.replace_n(o2, s) although o1 and o2 are the same in terms of key-val pairs? Yup, and that is unfortunate. I had only considered multiple evals of strings.replace_n(o1, s) which should now no longer have different outcomes.

Sorting them would guarantee stable output, of course, but it would also mean that there's no way for the user to control the replacement order. I think this comes down to the interface of strings.replace_n being unfortunate, it's using something unordered, so you cannot control the ordering, end of story.

Since nobody has ever been complaining about this, it's probably not a big deal, the common "foo -> bar, baz -> quz" usecase isn't affected.

tl;dr: I'll update this, sorting the keys, tomorrow. Controlling the replacement ordering is just not going to be supported. (Or stay unsupported, rather.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Technically, we could go back to using ast.JSON, and then sort the keys; but I think I like builtins.ObjectOperand better. WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

tl;dr: I'll update this, sorting the keys, tomorrow. Controlling the replacement ordering is just not going to be supported. (Or stay unsupported, rather.)

Sounds good. We can add that in the future if someone requests it.

Technically, we could go back to using ast.JSON, and then sort the keys; but I think I like builtins.ObjectOperand better. WDYT?

I'd probably just use the ast types. I'd call Keys() and then sort the result.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

✅ Updated

@srenatus srenatus force-pushed the sr/fix-2822/strings.replace_n branch 5 times, most recently from 8c957d5 to b83154d Compare October 29, 2020 10:05
tsandall
tsandall previously approved these changes Oct 30, 2020
Copy link
Member

@tsandall tsandall left a comment

Choose a reason for hiding this comment

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

LGTM

@tsandall
Copy link
Member

@srenatus can you squash the commits? Once that's done we can merge.

In the existing implementation, looping over the map was indeterministic,
and hence the result of subsequent evaluations of a string.replacen_n call
with overlapping patterns would be, too.

Now, the builtin will sort its keys before building the strings.Replacer.

Before, the ordering of overlapping patterns wasn't under the user's control,
and after, it still isn't. But it's deterministic.

Signed-off-by: Stephan Renatus <srenatus@chef.io>
@srenatus
Copy link
Contributor Author

@tsandall sure! ✔️

@tsandall tsandall merged commit a723462 into open-policy-agent:master Oct 30, 2020
@srenatus srenatus deleted the sr/fix-2822/strings.replace_n branch October 30, 2020 18:45
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.

builtin strings.replace_n is nondetermistic when patterns overlap
2 participants