Fork of the Fernflower decompiler
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fernflower/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ConstExprent.java

425 lines
15 KiB

/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* 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 org.jetbrains.java.decompiler.modules.decompiler.exps;
import org.jetbrains.java.decompiler.code.CodeConstants;
import org.jetbrains.java.decompiler.main.DecompilerContext;
import org.jetbrains.java.decompiler.main.TextBuffer;
import org.jetbrains.java.decompiler.main.collectors.BytecodeMappingTracer;
import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor;
import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor;
import org.jetbrains.java.decompiler.struct.gen.VarType;
import org.jetbrains.java.decompiler.struct.match.MatchEngine;
import org.jetbrains.java.decompiler.struct.match.MatchNode;
import org.jetbrains.java.decompiler.struct.match.MatchNode.RuleValue;
import org.jetbrains.java.decompiler.util.InterpreterUtil;
import org.jetbrains.java.decompiler.util.TextUtil;
import java.util.*;
import java.util.Map.Entry;
public class ConstExprent extends Exprent {
private static final Map<Integer, String> CHAR_ESCAPES;
static {
CHAR_ESCAPES = new HashMap<>();
CHAR_ESCAPES.put(0x8, "\\b"); /* \u0008: backspace BS */
CHAR_ESCAPES.put(0x9, "\\t"); /* \u0009: horizontal tab HT */
CHAR_ESCAPES.put(0xA, "\\n"); /* \u000a: linefeed LF */
CHAR_ESCAPES.put(0xC, "\\f"); /* \u000c: form feed FF */
CHAR_ESCAPES.put(0xD, "\\r"); /* \u000d: carriage return CR */
//CHAR_ESCAPES.put(0x22, "\\\""); /* \u0022: double quote " */
CHAR_ESCAPES.put(0x27, "\\\'"); /* \u0027: single quote ' */
CHAR_ESCAPES.put(0x5C, "\\\\"); /* \u005c: backslash \ */
}
private VarType constType;
private final Object value;
private final boolean boolPermitted;
public ConstExprent(int val, boolean boolPermitted, Set<Integer> bytecodeOffsets) {
this(guessType(val, boolPermitted), Integer.valueOf(val), boolPermitted, bytecodeOffsets);
}
public ConstExprent(VarType constType, Object value, Set<Integer> bytecodeOffsets) {
this(constType, value, false, bytecodeOffsets);
}
private ConstExprent(VarType constType, Object value, boolean boolPermitted, Set<Integer> bytecodeOffsets) {
super(EXPRENT_CONST);
this.constType = constType;
this.value = value;
this.boolPermitted = boolPermitted;
addBytecodeOffsets(bytecodeOffsets);
}
private static VarType guessType(int val, boolean boolPermitted) {
if (boolPermitted) {
VarType constType = VarType.VARTYPE_BOOLEAN;
if (val != 0 && val != 1) {
constType = constType.copy(true);
}
return constType;
}
else if (0 <= val && val <= 127) {
return VarType.VARTYPE_BYTECHAR;
}
else if (-128 <= val && val <= 127) {
return VarType.VARTYPE_BYTE;
}
else if (0 <= val && val <= 32767) {
return VarType.VARTYPE_SHORTCHAR;
}
else if (-32768 <= val && val <= 32767) {
return VarType.VARTYPE_SHORT;
}
else if (0 <= val && val <= 0xFFFF) {
return VarType.VARTYPE_CHAR;
}
else {
return VarType.VARTYPE_INT;
}
}
@Override
public Exprent copy() {
return new ConstExprent(constType, value, bytecode);
}
@Override
public VarType getExprType() {
return constType;
}
@Override
public int getExprentUse() {
return Exprent.MULTIPLE_USES | Exprent.SIDE_EFFECTS_FREE;
}
public List<Exprent> getAllExprents() {
return new ArrayList<>();
}
@Override
public TextBuffer toJava(int indent, BytecodeMappingTracer tracer) {
boolean literal = DecompilerContext.getOption(IFernflowerPreferences.LITERALS_AS_IS);
boolean ascii = DecompilerContext.getOption(IFernflowerPreferences.ASCII_STRING_CHARACTERS);
tracer.addMapping(bytecode);
if (constType.type != CodeConstants.TYPE_NULL && value == null) {
return new TextBuffer(ExprProcessor.getCastTypeName(constType));
}
switch (constType.type) {
case CodeConstants.TYPE_BOOLEAN:
return new TextBuffer(Boolean.toString(((Integer)value).intValue() != 0));
case CodeConstants.TYPE_CHAR:
Integer val = (Integer)value;
String ret = CHAR_ESCAPES.get(val);
if (ret == null) {
char c = (char)val.intValue();
if (c >= 32 && c < 127 || !ascii && TextUtil.isPrintableUnicode(c)) {
ret = String.valueOf(c);
}
else {
ret = TextUtil.charToUnicodeLiteral(c);
}
}
return new TextBuffer(ret).enclose("'", "'");
case CodeConstants.TYPE_BYTE:
case CodeConstants.TYPE_BYTECHAR:
case CodeConstants.TYPE_SHORT:
case CodeConstants.TYPE_SHORTCHAR:
case CodeConstants.TYPE_INT:
int intVal = ((Integer)value).intValue();
if (!literal) {
if (intVal == Integer.MAX_VALUE) {
return new FieldExprent("MAX_VALUE", "java/lang/Integer", true, null, FieldDescriptor.INTEGER_DESCRIPTOR, bytecode).toJava(0, tracer);
}
else if (intVal == Integer.MIN_VALUE) {
return new FieldExprent("MIN_VALUE", "java/lang/Integer", true, null, FieldDescriptor.INTEGER_DESCRIPTOR, bytecode).toJava(0, tracer);
}
}
return new TextBuffer(value.toString());
case CodeConstants.TYPE_LONG:
long longVal = ((Long)value).longValue();
if (!literal) {
if (longVal == Long.MAX_VALUE) {
return new FieldExprent("MAX_VALUE", "java/lang/Long", true, null, FieldDescriptor.LONG_DESCRIPTOR, bytecode).toJava(0, tracer);
}
else if (longVal == Long.MIN_VALUE) {
return new FieldExprent("MIN_VALUE", "java/lang/Long", true, null, FieldDescriptor.LONG_DESCRIPTOR, bytecode).toJava(0, tracer);
}
}
return new TextBuffer(value.toString()).append('L');
case CodeConstants.TYPE_FLOAT:
float floatVal = ((Float)value).floatValue();
if (!literal) {
if (Float.isNaN(floatVal)) {
return new FieldExprent("NaN", "java/lang/Float", true, null, FieldDescriptor.FLOAT_DESCRIPTOR, bytecode).toJava(0, tracer);
}
else if (floatVal == Float.POSITIVE_INFINITY) {
return new FieldExprent("POSITIVE_INFINITY", "java/lang/Float", true, null, FieldDescriptor.FLOAT_DESCRIPTOR, bytecode).toJava(0, tracer);
}
else if (floatVal == Float.NEGATIVE_INFINITY) {
return new FieldExprent("NEGATIVE_INFINITY", "java/lang/Float", true, null, FieldDescriptor.FLOAT_DESCRIPTOR, bytecode).toJava(0, tracer);
}
else if (floatVal == Float.MAX_VALUE) {
return new FieldExprent("MAX_VALUE", "java/lang/Float", true, null, FieldDescriptor.FLOAT_DESCRIPTOR, bytecode).toJava(0, tracer);
}
else if (floatVal == Float.MIN_VALUE) {
return new FieldExprent("MIN_VALUE", "java/lang/Float", true, null, FieldDescriptor.FLOAT_DESCRIPTOR, bytecode).toJava(0, tracer);
}
}
else if (Float.isNaN(floatVal)) {
return new TextBuffer("0.0F / 0.0");
}
else if (floatVal == Float.POSITIVE_INFINITY) {
return new TextBuffer("1.0F / 0.0");
}
else if (floatVal == Float.NEGATIVE_INFINITY) {
return new TextBuffer("-1.0F / 0.0");
}
return new TextBuffer(value.toString()).append('F');
case CodeConstants.TYPE_DOUBLE:
double doubleVal = ((Double)value).doubleValue();
if (!literal) {
if (Double.isNaN(doubleVal)) {
return new FieldExprent("NaN", "java/lang/Double", true, null, FieldDescriptor.DOUBLE_DESCRIPTOR, bytecode).toJava(0, tracer);
}
else if (doubleVal == Double.POSITIVE_INFINITY) {
return new FieldExprent("POSITIVE_INFINITY", "java/lang/Double", true, null, FieldDescriptor.DOUBLE_DESCRIPTOR, bytecode).toJava(0, tracer);
}
else if (doubleVal == Double.NEGATIVE_INFINITY) {
return new FieldExprent("NEGATIVE_INFINITY", "java/lang/Double", true, null, FieldDescriptor.DOUBLE_DESCRIPTOR, bytecode).toJava(0, tracer);
}
else if (doubleVal == Double.MAX_VALUE) {
return new FieldExprent("MAX_VALUE", "java/lang/Double", true, null, FieldDescriptor.DOUBLE_DESCRIPTOR, bytecode).toJava(0, tracer);
}
else if (doubleVal == Double.MIN_VALUE) {
return new FieldExprent("MIN_VALUE", "java/lang/Double", true, null, FieldDescriptor.DOUBLE_DESCRIPTOR, bytecode).toJava(0, tracer);
}
}
else if (Double.isNaN(doubleVal)) {
return new TextBuffer("0.0D / 0.0");
}
else if (doubleVal == Double.POSITIVE_INFINITY) {
return new TextBuffer("1.0D / 0.0");
}
else if (doubleVal == Double.NEGATIVE_INFINITY) {
return new TextBuffer("-1.0D / 0.0");
}
return new TextBuffer(value.toString()).append('D');
case CodeConstants.TYPE_NULL:
return new TextBuffer("null");
case CodeConstants.TYPE_OBJECT:
if (constType.equals(VarType.VARTYPE_STRING)) {
return new TextBuffer(convertStringToJava(value.toString(), ascii)).enclose("\"", "\"");
}
else if (constType.equals(VarType.VARTYPE_CLASS)) {
String stringVal = value.toString();
VarType type = new VarType(stringVal, !stringVal.startsWith("["));
return new TextBuffer(ExprProcessor.getCastTypeName(type)).append(".class");
}
}
throw new RuntimeException("invalid constant type: " + constType);
}
private static String convertStringToJava(String value, boolean ascii) {
char[] arr = value.toCharArray();
StringBuilder buffer = new StringBuilder(arr.length);
for (char c : arr) {
switch (c) {
case '\\': // u005c: backslash \
buffer.append("\\\\");
break;
case 0x8: // "\\\\b"); // u0008: backspace BS
buffer.append("\\b");
break;
case 0x9: //"\\\\t"); // u0009: horizontal tab HT
buffer.append("\\t");
break;
case 0xA: //"\\\\n"); // u000a: linefeed LF
buffer.append("\\n");
break;
case 0xC: //"\\\\f"); // u000c: form feed FF
buffer.append("\\f");
break;
case 0xD: //"\\\\r"); // u000d: carriage return CR
buffer.append("\\r");
break;
case 0x22: //"\\\\\""); // u0022: double quote "
buffer.append("\\\"");
break;
//case 0x27: //"\\\\'"); // u0027: single quote '
// buffer.append("\\\'");
// break;
default:
if (c >= 32 && c < 127 || !ascii && TextUtil.isPrintableUnicode(c)) {
buffer.append(c);
}
else {
buffer.append(TextUtil.charToUnicodeLiteral(c));
}
}
}
return buffer.toString();
}
@Override
public boolean equals(Object o) {
if (o == this) return true;
if (o == null || !(o instanceof ConstExprent)) return false;
ConstExprent cn = (ConstExprent)o;
return InterpreterUtil.equalObjects(constType, cn.getConstType()) &&
InterpreterUtil.equalObjects(value, cn.getValue());
}
@Override
public int hashCode() {
int result = constType != null ? constType.hashCode() : 0;
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
public boolean hasBooleanValue() {
switch (constType.type) {
case CodeConstants.TYPE_BOOLEAN:
case CodeConstants.TYPE_CHAR:
case CodeConstants.TYPE_BYTE:
case CodeConstants.TYPE_BYTECHAR:
case CodeConstants.TYPE_SHORT:
case CodeConstants.TYPE_SHORTCHAR:
case CodeConstants.TYPE_INT:
int value = ((Integer)this.value).intValue();
return value == 0 || (DecompilerContext.getOption(IFernflowerPreferences.BOOLEAN_TRUE_ONE) && value == 1);
}
return false;
}
public boolean hasValueOne() {
switch (constType.type) {
case CodeConstants.TYPE_BOOLEAN:
case CodeConstants.TYPE_CHAR:
case CodeConstants.TYPE_BYTE:
case CodeConstants.TYPE_BYTECHAR:
case CodeConstants.TYPE_SHORT:
case CodeConstants.TYPE_SHORTCHAR:
case CodeConstants.TYPE_INT:
return ((Integer)value).intValue() == 1;
case CodeConstants.TYPE_LONG:
return ((Long)value).intValue() == 1;
case CodeConstants.TYPE_DOUBLE:
return ((Double)value).intValue() == 1;
case CodeConstants.TYPE_FLOAT:
return ((Float)value).intValue() == 1;
}
return false;
}
public static ConstExprent getZeroConstant(int type) {
switch (type) {
case CodeConstants.TYPE_INT:
return new ConstExprent(VarType.VARTYPE_INT, Integer.valueOf(0), null);
case CodeConstants.TYPE_LONG:
return new ConstExprent(VarType.VARTYPE_LONG, Long.valueOf(0), null);
case CodeConstants.TYPE_DOUBLE:
return new ConstExprent(VarType.VARTYPE_DOUBLE, Double.valueOf(0), null);
case CodeConstants.TYPE_FLOAT:
return new ConstExprent(VarType.VARTYPE_FLOAT, Float.valueOf(0), null);
}
throw new RuntimeException("Invalid argument: " + type);
}
public VarType getConstType() {
return constType;
}
public void setConstType(VarType constType) {
this.constType = constType;
}
public void adjustConstType(VarType expectedType) {
// BYTECHAR and SHORTCHAR => CHAR in the CHAR context
if (expectedType.equals(VarType.VARTYPE_CHAR) &&
(constType.equals(VarType.VARTYPE_BYTECHAR) || constType.equals(VarType.VARTYPE_SHORTCHAR))) {
if (getIntValue() != 0) {
setConstType(VarType.VARTYPE_CHAR);
}
}
// CHAR => INT in the INT context
else if (expectedType.equals(VarType.VARTYPE_INT) &&
constType.equals(VarType.VARTYPE_CHAR)) {
setConstType(VarType.VARTYPE_INT);
}
}
public Object getValue() {
return value;
}
public int getIntValue() {
return ((Integer)value).intValue();
}
public boolean isBoolPermitted() {
return boolPermitted;
}
// *****************************************************************************
// IMatchable implementation
// *****************************************************************************
@Override
public boolean match(MatchNode matchNode, MatchEngine engine) {
if (!super.match(matchNode, engine)) {
return false;
}
for (Entry<MatchProperties, RuleValue> rule : matchNode.getRules().entrySet()) {
RuleValue value = rule.getValue();
MatchProperties key = rule.getKey();
if (key == MatchProperties.EXPRENT_CONSTTYPE) {
if (!value.value.equals(this.constType)) {
return false;
}
}
else if (key == MatchProperties.EXPRENT_CONSTVALUE) {
if (value.isVariable() && !engine.checkAndSetVariableValue(value.value.toString(), this.value)) {
return false;
}
}
}
return true;
}
}