-
Notifications
You must be signed in to change notification settings - Fork 20
Implement bits.* builtins #149
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,134 @@ | ||
| package io.github.open_policy_agent.opa.ast.builtin.impls; | ||
|
|
||
| import static io.github.open_policy_agent.opa.ast.builtin.impls.utils.ArgHelper.getArg; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.function.BiFunction; | ||
|
|
||
| import io.github.open_policy_agent.opa.ast.builtin.OpaBuiltin; | ||
| import io.github.open_policy_agent.opa.ast.builtin.OpaDynamic; | ||
| import io.github.open_policy_agent.opa.ast.builtin.OpaType; | ||
| import io.github.open_policy_agent.opa.ast.types.RegoBigInt; | ||
| import io.github.open_policy_agent.opa.ast.types.RegoNumber; | ||
| import io.github.open_policy_agent.opa.ast.types.RegoValue; | ||
| import io.github.open_policy_agent.opa.rego.EvaluationContext; | ||
| import io.github.open_policy_agent.opa.rego.TypeError; | ||
|
|
||
| public class BitsBuiltins { | ||
|
|
||
| public static Map<String, BiFunction<EvaluationContext, RegoValue[], RegoValue>> builtins() { | ||
| BitsBuiltins instance = new BitsBuiltins(); | ||
| return Map.ofEntries( | ||
| Map.entry("bits.and", instance::and), | ||
| Map.entry("bits.or", instance::or), | ||
| Map.entry("bits.xor", instance::xor), | ||
| Map.entry("bits.negate", instance::negate), | ||
| Map.entry("bits.lsh", instance::lsh), | ||
| Map.entry("bits.rsh", instance::rsh)); | ||
| } | ||
|
|
||
| @OpaBuiltin( | ||
| name = "bits.and", | ||
| description = "Returns the bitwise AND of two integers.", | ||
| args = { | ||
| @OpaType(type = "number", name = "x", description = "the first integer", dynamic = @OpaDynamic(type = "any")), | ||
| @OpaType(type = "number", name = "y", description = "the second integer", dynamic = @OpaDynamic(type = "any")) | ||
| }, | ||
| result = @OpaType(type = "number", name = "z", description = "the bitwise AND of x and y", dynamic = @OpaDynamic(type = "any"))) | ||
| public RegoValue and(EvaluationContext ctx, RegoValue[] args) { | ||
| RegoNumber x = getArg(args, 0, RegoNumber.class); | ||
| RegoNumber y = getArg(args, 1, RegoNumber.class); | ||
| requireInteger(x, "bits.and", 1); | ||
| requireInteger(y, "bits.and", 2); | ||
| return new RegoBigInt(x.getBigIntValue().and(y.getBigIntValue())); | ||
| } | ||
|
|
||
| @OpaBuiltin( | ||
| name = "bits.or", | ||
| description = "Returns the bitwise OR of two integers.", | ||
| args = { | ||
| @OpaType(type = "number", name = "x", description = "the first integer", dynamic = @OpaDynamic(type = "any")), | ||
| @OpaType(type = "number", name = "y", description = "the second integer", dynamic = @OpaDynamic(type = "any")) | ||
| }, | ||
| result = @OpaType(type = "number", name = "z", description = "the bitwise OR of x and y", dynamic = @OpaDynamic(type = "any"))) | ||
| public RegoValue or(EvaluationContext ctx, RegoValue[] args) { | ||
| RegoNumber x = getArg(args, 0, RegoNumber.class); | ||
| RegoNumber y = getArg(args, 1, RegoNumber.class); | ||
| requireInteger(x, "bits.or", 1); | ||
| requireInteger(y, "bits.or", 2); | ||
| return new RegoBigInt(x.getBigIntValue().or(y.getBigIntValue())); | ||
| } | ||
|
|
||
| @OpaBuiltin( | ||
| name = "bits.xor", | ||
| description = "Returns the bitwise XOR of two integers.", | ||
| args = { | ||
| @OpaType(type = "number", name = "x", description = "the first integer", dynamic = @OpaDynamic(type = "any")), | ||
| @OpaType(type = "number", name = "y", description = "the second integer", dynamic = @OpaDynamic(type = "any")) | ||
| }, | ||
| result = @OpaType(type = "number", name = "z", description = "the bitwise XOR of x and y", dynamic = @OpaDynamic(type = "any"))) | ||
| public RegoValue xor(EvaluationContext ctx, RegoValue[] args) { | ||
| RegoNumber x = getArg(args, 0, RegoNumber.class); | ||
| RegoNumber y = getArg(args, 1, RegoNumber.class); | ||
| requireInteger(x, "bits.xor", 1); | ||
| requireInteger(y, "bits.xor", 2); | ||
| return new RegoBigInt(x.getBigIntValue().xor(y.getBigIntValue())); | ||
| } | ||
|
|
||
| @OpaBuiltin( | ||
| name = "bits.negate", | ||
| description = "Returns the bitwise negation of an integer.", | ||
| args = { | ||
| @OpaType(type = "number", name = "x", description = "the integer to negate", dynamic = @OpaDynamic(type = "any")) | ||
| }, | ||
| result = @OpaType(type = "number", name = "z", description = "the bitwise negation of x", dynamic = @OpaDynamic(type = "any"))) | ||
| public RegoValue negate(EvaluationContext ctx, RegoValue[] args) { | ||
| RegoNumber x = getArg(args, 0, RegoNumber.class); | ||
| requireInteger(x, "bits.negate", 1); | ||
| return new RegoBigInt(x.getBigIntValue().not()); | ||
| } | ||
|
|
||
| @OpaBuiltin( | ||
| name = "bits.lsh", | ||
| description = "Returns the result of shifting x left by s bits.", | ||
| args = { | ||
| @OpaType(type = "number", name = "x", description = "the integer to shift", dynamic = @OpaDynamic(type = "any")), | ||
| @OpaType(type = "number", name = "s", description = "the number of bits to shift", dynamic = @OpaDynamic(type = "any")) | ||
| }, | ||
| result = @OpaType(type = "number", name = "z", description = "the result of shifting x s bits to the left", dynamic = @OpaDynamic(type = "any"))) | ||
| public RegoValue lsh(EvaluationContext ctx, RegoValue[] args) { | ||
| RegoNumber x = getArg(args, 0, RegoNumber.class); | ||
| RegoNumber s = getArg(args, 1, RegoNumber.class); | ||
| requireInteger(x, "bits.lsh", 1); | ||
| requireUnsigned(s, "bits.lsh"); | ||
| return new RegoBigInt(x.getBigIntValue().shiftLeft(s.getBigIntValue().intValueExact())); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| } | ||
|
|
||
| @OpaBuiltin( | ||
| name = "bits.rsh", | ||
| description = "Returns the result of shifting x right by s bits.", | ||
| args = { | ||
| @OpaType(type = "number", name = "x", description = "the integer to shift", dynamic = @OpaDynamic(type = "any")), | ||
| @OpaType(type = "number", name = "s", description = "the number of bits to shift", dynamic = @OpaDynamic(type = "any")) | ||
| }, | ||
| result = @OpaType(type = "number", name = "z", description = "the result of shifting x s bits to the right", dynamic = @OpaDynamic(type = "any"))) | ||
| public RegoValue rsh(EvaluationContext ctx, RegoValue[] args) { | ||
| RegoNumber x = getArg(args, 0, RegoNumber.class); | ||
| RegoNumber s = getArg(args, 1, RegoNumber.class); | ||
| requireInteger(x, "bits.rsh", 1); | ||
| requireUnsigned(s, "bits.rsh"); | ||
| return new RegoBigInt(x.getBigIntValue().shiftRight(s.getBigIntValue().intValueExact())); | ||
| } | ||
|
|
||
| private static void requireInteger(RegoNumber n, String fn, int operandIndex) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Integer-valued floats are wrongly rejected across all six functions. |
||
| if (n.isDecimal()) { | ||
| throw new TypeError(fn + ": operand " + operandIndex + " must be integer number but got floating-point number"); | ||
| } | ||
| } | ||
|
|
||
| private static void requireUnsigned(RegoNumber s, String fn) { | ||
| if (s.getBigIntValue().signum() < 0) { | ||
| throw new TypeError(fn + ": operand 2 must be an unsigned integer number but got a negative integer"); | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shift operand
sis never validated as an integer, so fractional shifts are silently truncated instead of erroring.lsh/rshcallrequireIntegeronxbut onlyrequireUnsignedons— neverrequireInteger(s, ...). Forbits.lsh(1, 2.5),RegoDecimal.getBigIntValue()doesBigInteger.valueOf((long) 2.5)= 2, so this returns 4. OPA raisesbits.lsh: operand 2 must be integer number but got floating-point number(undefined without--strict-builtin-errors,eval_type_errorwith it) — confirmed on the OPA binary. Reachable via JSON input{"s": 2.5}. Same issue inrshat line 120.