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.
425 lines
15 KiB
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;
|
|
}
|
|
} |