GP-5196 Adding support for expressions and symbol names in address fields

This commit is contained in:
ghidragon 2024-12-31 16:00:39 -05:00
parent 14527b015e
commit b1d257150c
61 changed files with 2902 additions and 1445 deletions

View file

@ -0,0 +1,24 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.expressions;
/**
* Base marker interface for {@link ExpressionGrouper}, {@link ExpressionOperator},
* and {@link ExpressionValue}
*/
public interface ExpressionElement {
// marker interface
}

View file

@ -0,0 +1,509 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.expressions;
import static generic.expressions.ExpressionGrouper.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import ghidra.util.NumericUtilities;
/**
* Class for evaluating numeric expressions. See
* {@link ExpressionOperator} for the full list of supported operators. All values are interpreted
* as longs. Optionally, an ExpressionEvalualuator can be constructed with a symbol evaluator that
* will be called on any string that can't be evaluated as an operator or number.
* <P>
* ExpressionEvaluators can operate in either decimal or hex mode. If in hex mode, all numbers are
* assumed to be hexadecimal values. In decimal mode, numbers are assumed to be decimal values, but
* hexadecimal values can still be specified by prefixing them with "0x".
* <P>
* There are also two convenience static methods that can be called to evaluate expressions. These
* methods will either return a Long value as the result or null if there was an error evaluating
* the expression. To get error messages related to parsing the expression, instantiate an
* ExpressionEvaluator and call {@link #parse(String)} which will throw a
* {@link ExpressionException} when the expression can't be evaluated.
*/
public class ExpressionEvaluator {
private static final String TOKEN_CHARS = "+-*/()<>|^&~ =!";
private boolean assumeHex = false;
private Function<String, ExpressionValue> evaluator;
/**
* Evaluates the given input as a Long value. This call assumes all numbers are decimal unless
* prefixed with a "0x".
* @param input the expression to be parsed into a Long value
* @return the resulting Long value or null if the expression could not be evaluated.
*/
public static Long evaluateToLong(String input) {
return evaluateToLong(input, false);
}
/**
* Evaluates the given input as a long value.
* @param input the expression to be parsed into a Long value
* @param assumeHex if true, numbers will be assumed to be hexadecimal values.
* @return the resulting Long value or null if the expression could not be evaluated.
*/
public static Long evaluateToLong(String input, boolean assumeHex) {
ExpressionEvaluator evaluator = new ExpressionEvaluator(assumeHex);
try {
return evaluator.parseAsLong(input);
}
catch (ExpressionException e) {
return null;
}
}
/**
* Constructs an ExpressionEvaluator in decimal mode.
*/
public ExpressionEvaluator() {
this(false);
}
/**
* Constructs an ExpressionEvaluator in either decimal or hex mode.
* @param assumeHex if true, the evaluator will assume all values are hexadecimal.
*/
public ExpressionEvaluator(boolean assumeHex) {
this(assumeHex, s -> null);
}
/**
* Constructs an ExpressionEvaluator in decimal mode with a given symbol evaluator.
* @param evaluator A function that can convert a string token into a value (Must be Long
* ExpressionValues, unless this is being called by a subclass that can handle other types
* of operand values)
*/
public ExpressionEvaluator(Function<String, ExpressionValue> evaluator) {
this(false, evaluator);
}
/**
* Constructs an ExpressionEvaluator in either decimal or hex mode with a given symbol
* evaluator.
* @param assumeHex if true, the evaluator will assume all values are hexadecimal.
* @param evaluator A function that can convert a string token into a value (Must be Long
* ExpressionValues, unless this is being called by a subclass that can handle other types
* of operand values)
*/
public ExpressionEvaluator(boolean assumeHex, Function<String, ExpressionValue> evaluator) {
this.assumeHex = assumeHex;
this.evaluator = Objects.requireNonNull(evaluator);
}
/**
* Parses the given expression input, expecting the result to be long value.
* @param input the expression string
* @return the long value result.
* @throws ExpressionException if the expression could not be evaluated to a long value.
*/
public long parseAsLong(String input) throws ExpressionException {
ExpressionValue expressionValue = parse(input);
if (expressionValue instanceof LongExpressionValue longValue) {
return longValue.getLongValue();
}
throw new ExpressionException("Expression did not evalute to a long! Got a " +
expressionValue.getClass() + " instead.");
}
/**
* Changes the hex/decimal mode.
* @param b if true, all numbers will be assumed to be hexadecimal
*/
public void setAssumeHex(boolean b) {
this.assumeHex = b;
}
protected ExpressionValue parse(String input) throws ExpressionException {
return this.parse(input, null);
}
protected ExpressionValue parse(String input, ExpressionValue initial)
throws ExpressionException {
List<ExpressionElement> list = new ArrayList<>();
// if there is a given initial value (used for relative expressions), add it to the
// sequential list of valid expression elements.
if (initial != null) {
list.add(initial);
}
// convert the text input into a list of valid expression elements
parseToList(input, list);
// evaluate the list of expression elements in operator precedence order.
return eval(list);
}
/**
* Parses the input string into a list of valid elements. When this method completes, the list
* will contain only valid operators, valid operand values, or group operators.
* @param input the input string to be parsed.
* @param list the list to populate with valid elements.
* @throws ExpressionException if any part of the input string can't be parsed into a valid
* expression element.
*/
private void parseToList(String input, List<ExpressionElement> list)
throws ExpressionException {
LookAheadTokenizer parser = new LookAheadTokenizer(input);
while (parser.hasMoreTokens()) {
String token = parser.getCurrentToken();
if (token.isBlank()) {
parser.advance(1);
}
else if (processGroupToken(list, token)) {
parser.advance(1);
}
else if (processOperator(list, token, parser.getNextToken())) {
ExpressionOperator op = getLastOperator(list);
parser.advance(op.size());
}
else if (processNumber(list, token)) {
parser.advance(1);
}
else if (processSymbol(list, token)) {
parser.advance(1);
}
else {
throw new ExpressionException("Could not evaluate token \"" + token + "\"");
}
}
if (list.isEmpty()) {
throw new ExpressionException("Expression is empty. Nothing to parse!");
}
}
/**
* Evaluates a list of valid expression elements into a single final value.
* @param list the list of valid expression elements.
* @return the final value the expression evaluates to
* @throws ExpressionException if sequence of expression elements is not in a valid order such
* that it evaluates to a single value. (such as two values not being separated by an operator)
*/
private ExpressionValue eval(List<ExpressionElement> list) throws ExpressionException {
// first evaluate any sub-lists grouped by parenthesis
processGroups(list);
// next process any unary operators
processUnaryOperators(list);
// final process binary operators in operator precedence order.
processBinaryOperators(list);
// if everything evaluated properly, there should only be one item left in the list
if (list.size() != 1) {
String result = list.stream().map(Object::toString).collect(Collectors.joining(" "));
throw new ExpressionException("Parse failed! Stopped at \"" + result + "\"");
}
ExpressionElement element = list.get(0);
if (element instanceof ExpressionValue ev) {
return ev;
}
throw new ExpressionException("Parse failed to evaluate to a value! Stopped at " + element);
}
private void processBinaryOperators(List<ExpressionElement> list) throws ExpressionException {
List<Set<ExpressionOperator>> ops = ExpressionOperator.getBinaryOperatorsByPrecedence();
// Each set in the list contains operators at the same precedence, so they all need
// to be processed at the same time so that they are processed left to right (which
// corresponds to the list order)
for (Set<ExpressionOperator> set : ops) {
processBinaryOperators(list, set);
}
}
private void processBinaryOperators(List<ExpressionElement> list,
Set<ExpressionOperator> operators) throws ExpressionException {
// can't have a valid binary operator at index 0, so start looking at index 1
int operatorIndex = findValidBinaryOperator(list, operators, 1);
while (operatorIndex >= 0) {
// we can safely cast here because we checked in the findValidBinaryOperator method
ExpressionOperator operator = (ExpressionOperator) list.get(operatorIndex);
ExpressionValue value1 = (ExpressionValue) list.get(operatorIndex - 1);
ExpressionValue value2 = (ExpressionValue) list.get(operatorIndex + 1);
ExpressionValue newValue = value1.applyBinaryOperator(operator, value2);
list.set(operatorIndex - 1, newValue);
list.subList(operatorIndex, operatorIndex + 2).clear();
// After the operator completed, the list has been changed and the sequence
// "value operator value" has been replace with the resulting value of the operation.
// Now look for the next operator in the current set of operators we are evaluating
operatorIndex = findValidBinaryOperator(list, operators, operatorIndex);
}
}
private int findValidBinaryOperator(List<ExpressionElement> list,
Set<ExpressionOperator> operators, int startIndex) {
// can't have a valid binary operator at the last index, so stop 1 before last
for (int i = startIndex; i < list.size() - 1; i++) {
if (operators.contains(list.get(i))) {
// make sure the elements before and after the operator are value types so
// that the caller can just cast them as values. If they are not, then
// this operator won't be evaluated and at the end the evaluate process, the list
// won't be reduced to just one element.
if (list.get(i - 1) instanceof ExpressionValue &
list.get(i + 1) instanceof ExpressionValue) {
return i;
}
}
}
return -1;
}
private void processUnaryOperators(List<ExpressionElement> list) throws ExpressionException {
int unaryOperatorIndex = findValidUnaryOperator(list);
while (unaryOperatorIndex >= 0) {
ExpressionOperator operator = (ExpressionOperator) list.get(unaryOperatorIndex);
ExpressionValue value = (ExpressionValue) list.get(unaryOperatorIndex + 1);
ExpressionValue newValue = value.applyUnaryOperator(operator);
list.remove(unaryOperatorIndex);
list.set(unaryOperatorIndex, newValue);
unaryOperatorIndex = findValidUnaryOperator(list);
}
}
private int findValidUnaryOperator(List<ExpressionElement> list) {
// stop 1 before end since you can't end in a valid unary operator
for (int i = 0; i < list.size() - 1; i++) {
// check any element in the list if it is a unary operator
if (list.get(i) instanceof ExpressionOperator op) {
if (!op.isUnary()) {
continue;
}
// make sure the next element is a value so the the caller can cast without fear
if (list.get(i + 1) instanceof ExpressionValue) {
return i;
}
}
}
return -1;
}
/**
* Recursively for groups (sublists surrounded by parenthesis) and process the sub list of
* elements in the group before processing the outer list. As each group is evaluated, the start
* paren operator, the end paren operator and all the tokens in between are replaced by the
* single value the group evaluated to.
* @param list the list to look for grouped sub-lists.
* @throws ExpressionException if a exception occurs processing a sub list
*/
private void processGroups(List<ExpressionElement> list) throws ExpressionException {
int groupStart = findGroupStart(list);
while (groupStart >= 0) {
int groupEndIndex = findGroupEnd(list, groupStart);
if (groupEndIndex < 0) {
throw new ExpressionException("Missing end parenthesis!");
}
ExpressionValue value = eval(list.subList(groupStart + 1, groupEndIndex));
// After evaluating, everything between the parens will be replaced by the result. So
// replace the left paren with the results and clear the next 2 entries.
list.set(groupStart, value);
list.subList(groupStart + 1, groupStart + 3).clear();
groupStart = findGroupStart(list);
}
}
private int findGroupStart(List<ExpressionElement> list) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i) == LEFT_PAREN) {
return i;
}
}
return -1;
}
private int findGroupEnd(List<ExpressionElement> list, int groupStart) {
int depth = 1;
for (int i = groupStart + 1; i < list.size(); i++) {
Object obj = list.get(i);
if (obj == LEFT_PAREN) {
depth++;
}
else if (obj == RIGHT_PAREN) {
if (--depth == 0) {
return i;
}
}
}
return -1;
}
// A tokenizer that keeps track of one future token. This is so the parser can handle operator
// chars that can ether be an operator by itself or part of a 2 char operator(i.e. "<", "=",
// "<=", "<<", "==")
private class LookAheadTokenizer {
private StringTokenizer tokenizer;
private String currentToken;
private String nextToken;
LookAheadTokenizer(String input) {
tokenizer = new StringTokenizer(input, TOKEN_CHARS, true);
currentToken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
nextToken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
}
public boolean hasMoreTokens() {
return currentToken != null;
}
public String getCurrentToken() {
return currentToken;
}
public String getNextToken() {
return nextToken;
}
public void advance(int count) {
for (int i = 0; i < count; i++) {
currentToken = nextToken;
nextToken = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
}
}
}
private ExpressionOperator getLastOperator(List<ExpressionElement> list) {
ExpressionElement lastElement = list.get(list.size() - 1);
return (ExpressionOperator) lastElement;
}
private boolean processSymbol(List<ExpressionElement> list, String token) {
ExpressionValue value = evaluateSymbol(token);
if (value != null) {
list.add(value);
return true;
}
return false;
}
protected ExpressionValue evaluateSymbol(String token) {
return evaluator.apply(token);
}
private boolean processOperator(List<ExpressionElement> list, String token, String nextToken) {
boolean preferBinary = shouldPreferBinaryOp(list);
ExpressionOperator op = ExpressionOperator.getOperator(token, nextToken, preferBinary);
if (op != null) {
list.add(op);
return true;
}
return false;
}
private boolean processGroupToken(List<ExpressionElement> list, String token) {
if (token.equals("(")) {
list.add(LEFT_PAREN);
return true;
}
if (token.equals(")")) {
list.add(RIGHT_PAREN);
return true;
}
return false;
}
private boolean shouldPreferBinaryOp(List<ExpressionElement> list) {
if (list.isEmpty()) {
return false;
}
ExpressionElement lastElement = list.get(list.size() - 1);
if (lastElement instanceof ExpressionValue) {
return true;
}
if (lastElement instanceof ExpressionOperator) {
return false;
}
if (lastElement == ExpressionGrouper.LEFT_PAREN) {
return false;
}
if (lastElement == ExpressionGrouper.RIGHT_PAREN) {
return true;
}
return false;
}
private boolean processNumber(List<ExpressionElement> list, String token) {
int radix = 10;
if (assumeHex && processAsHexNumber(list, token)) {
return true;
}
token = toLowerAndRemoveEndNumberDecorators(token);
if (token.startsWith("0x")) {
radix = 16;
token = token.substring(2);
}
try {
long value = (radix == 10) ? NumericUtilities.parseLong(token)
: NumericUtilities.parseHexLong(token);
list.add(new LongExpressionValue(value));
return true;
}
catch (Exception e) {
return false;
}
}
private String toLowerAndRemoveEndNumberDecorators(String token) {
token = token.toLowerCase();
if (token.endsWith("ull") || token.endsWith("llu")) {
token = token.substring(0, token.length() - 3);
}
else if (token.endsWith("ul") || token.endsWith("lu") || token.endsWith("ll")) {
token = token.substring(0, token.length() - 2);
}
else if (token.endsWith("l") || token.endsWith("u")) {
token = token.substring(0, token.length() - 1);
}
return token;
}
// parses values as a hex value (e.g. parsing "10" returns 16 instead of 10)
private boolean processAsHexNumber(List<ExpressionElement> list, String token) {
try {
long value = NumericUtilities.parseHexLong(token);
list.add(new LongExpressionValue(value));
return true;
}
catch (NumberFormatException e) {
// ignore
}
return false;
}
}

View file

@ -0,0 +1,25 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.expressions;
/**
* Exception thrown when using an {@link ExpressionEvaluator}
*/
public class ExpressionException extends Exception {
public ExpressionException(String message) {
super(message);
}
}

View file

@ -0,0 +1,23 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.expressions;
/**
* Grouping {@link ExpressionElement}s
*/
public enum ExpressionGrouper implements ExpressionElement {
LEFT_PAREN, RIGHT_PAREN;
}

View file

@ -0,0 +1,172 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.expressions;
import java.util.*;
import org.apache.commons.collections4.map.LazyMap;
/**
* Enum of support operators for the {@link ExpressionEvaluator}
*/
public enum ExpressionOperator implements ExpressionElement {
// unary
BITWISE_NOT("~", OpType.UNARY, 1),
LOGICAL_NOT("!", OpType.UNARY, 1),
UNARY_PLUS("+", OpType.UNARY, 1),
UNARY_MINUS("-", OpType.UNARY, 1),
// multiplicative
MULTIPLY("*", OpType.BINARY, 2),
DIVIDE("/", OpType.BINARY, 2),
// additive
ADD("+", OpType.BINARY, 3),
SUBTRACT("-", OpType.BINARY, 3),
// shift
SHIFT_LEFT("<<", OpType.BINARY, 4),
SHIFT_RIGHT(">>", OpType.BINARY, 4),
// relational
LESS_THAN("<", OpType.BINARY, 5),
GREATER_THAN(">", OpType.BINARY, 5),
LESS_THAN_OR_EQUAL("<=", OpType.BINARY, 5),
GREATER_THAN_OR_EQUAL(">=", OpType.BINARY, 5),
// equality
EQUALS("==", OpType.BINARY, 6),
NOT_EQUALS("!=", OpType.BINARY, 6),
// bitwise
BITWISE_AND("&", OpType.BINARY, 7),
BITWISE_XOR("^", OpType.BINARY, 8),
BITWISE_OR("|", OpType.BINARY, 9),
// logical
LOGICAL_AND("&&", OpType.BINARY, 10),
LOGICAL_OR("||", OpType.BINARY, 11);
public static List<Set<ExpressionOperator>> binaryOperatorsByPrecedence;
private String name;
private OpType type;
private int precedence;
private ExpressionOperator(String name, OpType type, int precedence) {
this.name = name;
this.type = type;
this.precedence = precedence;
}
@Override
public String toString() {
return name;
}
/**
* Returns a list of all the binary operators in precedence order, organized into sets where
* each set contains all the operators of the same precedence.
* @return a list of all the binary operators in precedence order, organized into sets where
* each set contains all the operators of the same precedence.
*/
public static List<Set<ExpressionOperator>> getBinaryOperatorsByPrecedence() {
if (binaryOperatorsByPrecedence == null) {
binaryOperatorsByPrecedence = buildOperatorsByPrecedenceList();
}
return binaryOperatorsByPrecedence;
}
private static List<Set<ExpressionOperator>> buildOperatorsByPrecedenceList() {
ExpressionOperator[] values = values();
LazyMap<Integer, HashSet<ExpressionOperator>> map =
LazyMap.lazyMap(new TreeMap<>(), k -> new HashSet<>());
for (ExpressionOperator op : values) {
if (op.isBinary()) {
map.get(op.precedence).add(op);
}
}
return new ArrayList<>(map.values());
}
/**
* Returns the operator for the given token and look ahead token and if we are expecting to find
* a binary operator. This method first tries merging the tokens looking for a double char
* operator first.
* @param token the first token
* @param lookahead1 the next token that may or may not be part of this operand
* @param preferBinary if we are expecting a binary operator (the previous expression element
* was an operand value). We need this to know if the token '-' is the unary operator or the
* binary operator. If the token before was an operator, then we expect a unary operator. If
* the previous was a value, then we expect a binary operator.
* @return the operator that matches the given tokens and expected type
*/
public static ExpressionOperator getOperator(String token, String lookahead1,
boolean preferBinary) {
if (lookahead1 != null) {
String doubleToken = token + lookahead1;
ExpressionOperator operator = findOperator(doubleToken, preferBinary);
if (operator != null) {
return operator;
}
}
return findOperator(token, preferBinary);
}
private static ExpressionOperator findOperator(String tokens, boolean expectBinary) {
for (ExpressionOperator operator : values()) {
if (operator.name.equals(tokens)) {
if (operator.isBinary() == expectBinary) {
return operator;
}
}
}
return null;
}
/**
* Returns the number of chars in the operator
* @return the number of chars in the operator
*/
public int size() {
return name.length();
}
/**
* Returns if the operator is a unary operator.
* @return if the operator is a unary operator.
*/
public boolean isUnary() {
return type == OpType.UNARY;
}
/**
* Returns if the operator is a binary operator.
* @return if the operator is a binary operator.
*/
public boolean isBinary() {
return type == OpType.BINARY;
}
private enum OpType {
UNARY, BINARY
}
}

View file

@ -0,0 +1,42 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.expressions;
/**
* Operand types use by the {@link ExpressionEvaluator} must implement this interface.
*/
public interface ExpressionValue extends ExpressionElement {
/**
* Method called to apply a unary operator to this value.
* @param operator the operator being applied
* @return the new value after the operator is applied to this value
* @throws ExpressionException if the operator is not applicable for this value
*/
public ExpressionValue applyUnaryOperator(ExpressionOperator operator) throws ExpressionException;
/**
* Method called to apply a binary operator to this value.
* @param operator the binary operator being applied.
* @param value the other value to combine with this value by the operator
* @return the new value after the operator is applied to this value
* @throws ExpressionException if the operator is not applicable for this value or the the other
* value is not applicable for this operand and operator
*/
public ExpressionValue applyBinaryOperator(ExpressionOperator operator, ExpressionValue value)
throws ExpressionException;
}

View file

@ -0,0 +1,110 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.expressions;
/**
* Long operand values. See {@link ExpressionValue}. Defines supported operators and other
* operands for expression values that are long values.
*/
public class LongExpressionValue implements ExpressionValue {
private final long value;
public LongExpressionValue(long value) {
this.value = value;
}
public long getLongValue() {
return value;
}
@Override
public String toString() {
return Long.toString(value);
}
@Override
public ExpressionValue applyUnaryOperator(ExpressionOperator operator) throws ExpressionException {
switch (operator) {
case BITWISE_NOT:
return new LongExpressionValue(~value);
case LOGICAL_NOT:
return new LongExpressionValue(value == 0 ? 1 : 0);
case UNARY_MINUS:
return new LongExpressionValue(-value);
case UNARY_PLUS:
return this;
default:
throw new ExpressionException(
"Unary Operator " + operator + " not supported by Long values!");
}
}
@Override
public ExpressionValue applyBinaryOperator(ExpressionOperator operator, ExpressionValue operand)
throws ExpressionException {
if (!(operand instanceof LongExpressionValue longOperand)) {
throw new ExpressionException("Unsupported operand type for Long: " + value);
}
long otherValue = longOperand.value;
switch (operator) {
case BITWISE_AND:
return new LongExpressionValue(value & otherValue);
case BITWISE_OR:
return new LongExpressionValue(value | otherValue);
case BITWISE_XOR:
return new LongExpressionValue(value ^ otherValue);
case DIVIDE:
return new LongExpressionValue(value / otherValue);
case EQUALS:
return new LongExpressionValue(value == otherValue ? 1 : 0);
case GREATER_THAN:
return new LongExpressionValue(value > otherValue ? 1 : 0);
case GREATER_THAN_OR_EQUAL:
return new LongExpressionValue(value >= otherValue ? 1 : 0);
case SHIFT_LEFT:
return new LongExpressionValue(value << otherValue);
case LESS_THAN:
return new LongExpressionValue(value < otherValue ? 1 : 0);
case LESS_THAN_OR_EQUAL:
return new LongExpressionValue(value <= otherValue ? 1 : 0);
case LOGICAL_AND:
int b1 = value == 0 ? 0 : 1;
int b2 = otherValue == 0 ? 0 : 1;
return new LongExpressionValue(b1 & b2);
case LOGICAL_OR:
b1 = value == 0 ? 0 : 1;
b2 = otherValue == 0 ? 0 : 1;
return new LongExpressionValue(b1 | b2);
case SUBTRACT:
return new LongExpressionValue(value - otherValue);
case NOT_EQUALS:
return new LongExpressionValue(value == otherValue ? 0 : 1);
case ADD:
return new LongExpressionValue(value + otherValue);
case SHIFT_RIGHT:
return new LongExpressionValue(value >> otherValue);
case MULTIPLY:
return new LongExpressionValue(value * otherValue);
default:
throw new ExpressionException(
"Binary Operator \"" + operator + "\" not supported by Long values!");
}
}
}

View file

@ -0,0 +1,206 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package generic.expressions;
import static org.junit.Assert.*;
import org.junit.Test;
public class ExpressionEvaluatorTest {
@Test
public void testUnaryPlus() {
assertEval(100, "+100");
}
@Test
public void testUnaryMinus() {
assertEval(-100, "-100");
}
@Test
public void testAdd() {
assertEval(100, "20+10 + 70");
}
@Test
public void testSubtract() {
assertEval(5, "20-10-5");
assertEval(5, "20-5-10");
}
@Test
public void testMultiply() {
assertEval(42, "2*3*7");
}
@Test
public void testDivide() {
assertEval(1, "8/4/2");
assertEval(1, "5/4");
}
@Test
public void testShiftLeft() {
assertEval(4, "16>>2");
}
@Test
public void testShiftRight() {
assertEval(8, "1<<3");
}
@Test
public void testBitWiseNot() {
assertEval(-1, "~0");
}
@Test
public void testLogicalNot() {
assertEval(0, "!1");
assertEval(1, "!0");
assertEval(0, "!124");
}
@Test
public void testGreaterThan() {
assertEval(1, "8>5");
assertEval(0, "5>8");
assertEval(0, "8>8");
}
@Test
public void testLessThan() {
assertEval(0, "8<5");
assertEval(1, "5<8");
assertEval(0, "8<8");
}
@Test
public void testGreaterThanOrEqual() {
assertEval(1, "8>=5");
assertEval(0, "5>=8");
assertEval(1, "8>=8");
}
@Test
public void testLessThanOrEqual() {
assertEval(0, "8<=5");
assertEval(1, "5<=8");
assertEval(1, "8<=8");
}
@Test
public void testAddSubtractAssociatesLeftToRight() {
assertEval(110, "100+30-10-10");
assertEval(110, "100-10-10+30");
}
@Test
public void testMultiplyDivide() {
assertEval(100, "10*30/3");
assertEval(90, "10/3*30");
}
@Test
public void testBitwiseAnd() {
assertEval(0x4, "0xffff & 0x4");
assertEval(0x0, "0x4 & 0x2");
}
@Test
public void testBitwiseOr() {
assertEval(0x6, "0x2 | 0x4");
}
@Test
public void testBitwiseXor() {
assertEval(0x2, "0x3 ^ 0x1");
}
@Test
public void testLogicalAnd() {
assertEval(1, "0xffff && 0x4");
assertEval(0, "0x4 && 0");
}
@Test
public void testLogicalOr() {
assertEval(1, "0x2 || 0x4");
assertEval(0, "0 || 0");
}
@Test
public void testMixedPrecedence() {
assertEval(23, "10+3*5-8/4");
}
@Test
public void testGrouping() {
assertEval(42, "6*(3+4)");
assertEval(16, "1 << (8/2)");
assertEval(-1, "~(-1+1)");
assertEval(13, "1+(3 * (7+1)/2)");
}
@Test
public void testStackedUnaryOperators() {
assertEval(-1, "~~~0");
}
@Test
public void testInvalidSyntax() {
assertEvalNull("5 5");
assertEvalNull("+");
assertEvalNull("<< 5");
assertEvalNull("5 +");
assertEvalNull("(3+2");
}
@Test
public void testMixedValues() {
assertEval(26, "10+0x10");
}
@Test
public void testHexOnly() {
assertEvalHexOnly(22, "10+6");
}
private void assertEval(long expected, String expression) {
long result = ExpressionEvaluator.evaluateToLong(expression);
assertEquals(expected, result);
}
private void assertEvalHexOnly(long expected, String expression) {
ExpressionEvaluator evaluator = new ExpressionEvaluator(true);
long result;
try {
result = evaluator.parseAsLong(expression);
assertEquals(expected, result);
}
catch (ExpressionException e) {
// ignore
}
}
private void assertEvalNull(String expression) {
assertNull(ExpressionEvaluator.evaluateToLong(expression));
}
}