org.teavm.debugging.WasmValueImpl Maven / Gradle / Ivy
/*
* Copyright 2022 Alexey Andreev.
*
* 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.teavm.debugging;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.teavm.backend.wasm.debug.info.ArrayLayout;
import org.teavm.backend.wasm.debug.info.ClassLayout;
import org.teavm.backend.wasm.debug.info.DebugInfo;
import org.teavm.backend.wasm.debug.info.FieldType;
import org.teavm.backend.wasm.debug.info.InterfaceLayout;
import org.teavm.backend.wasm.debug.info.PrimitiveLayout;
import org.teavm.backend.wasm.debug.info.TypeLayout;
import org.teavm.common.Promise;
import org.teavm.debugging.javascript.JavaScriptCallFrame;
import org.teavm.debugging.javascript.JavaScriptValue;
import org.teavm.model.PrimitiveType;
class WasmValueImpl extends Value {
private static final String CLASS_PROP = "__class";
private static final String ADDRESS_PROP = "__address";
private DebugInfo debugInfo;
private FieldType type;
private JavaScriptCallFrame callFrame;
private long longValue;
private Promise calculatedType;
WasmValueImpl(Debugger debugger, DebugInfo debugInfo, FieldType type, JavaScriptCallFrame callFrame,
long longValue) {
super(debugger);
this.debugInfo = debugInfo;
this.type = type;
this.callFrame = callFrame;
this.longValue = longValue;
}
@Override
public Promise getRepresentation() {
switch (type) {
case BOOLEAN:
return Promise.of(longValue != 0 ? "true" : "false");
case BYTE:
return Promise.of(Byte.toString((byte) longValue));
case SHORT:
return Promise.of(Short.toString((short) longValue));
case CHAR: {
var sb = new StringBuilder("'");
appendChar(sb, (char) longValue);
sb.append("'");
return Promise.of(sb.toString());
}
case INT:
return Promise.of(Integer.toString((int) longValue));
case LONG:
return Promise.of(Long.toString(longValue));
case FLOAT:
return Promise.of(Float.toString(Float.intBitsToFloat((int) longValue)));
case DOUBLE:
return Promise.of(Double.toString(Double.longBitsToDouble(longValue)));
case OBJECT:
return buildObjectRepresentation();
case ADDRESS:
return Promise.of("0x" + Integer.toHexString((int) longValue));
default:
return Promise.of("undefined");
}
}
private void appendChar(StringBuilder sb, char c) {
switch (c) {
case '\n':
sb.append("\\n");
break;
case '\r':
sb.append("\\r");
break;
case '\t':
sb.append("\\t");
break;
case '\b':
sb.append("\\b");
break;
case '\f':
sb.append("\\f");
break;
case '\'':
sb.append("\\\'");
break;
case '\"':
sb.append("\\\"");
break;
case '\\':
sb.append("\\\\");
break;
default:
if (c < 32) {
sb.append("\\u00").append(Character.forDigit(c / 16, 16))
.append(Character.forDigit(c % 16, 16));
} else {
sb.append(c);
}
break;
}
}
private Promise buildObjectRepresentation() {
if (longValue == 0) {
return Promise.of("null");
}
return getCalculatedType().thenAsync(cls -> {
if (cls == null) {
return Promise.of("error");
}
return typeRepresentation(cls, (int) longValue);
});
}
private Promise typeRepresentation(TypeLayout type, int address) {
switch (type.kind()) {
case CLASS:
return objectRepresentation((ClassLayout) type, address);
case ARRAY:
return arrayRepresentation((ArrayLayout) type, address);
default:
break;
}
return Promise.of(classToString(type));
}
private Promise objectRepresentation(ClassLayout cls, int address) {
if (cls.classRef().fullName().equals("java.lang.String")) {
var stringRepr = decodeString(cls, address);
if (stringRepr != null) {
return stringRepr.then(result -> result != null ? result : classToString(cls));
}
} else if (cls.classRef().fullName().equals("java.lang.Class")) {
var stringRepr = decodeClass(address);
if (stringRepr != null) {
return Promise.of(stringRepr);
}
}
return Promise.of(classToString(cls));
}
private Promise arrayRepresentation(ArrayLayout arrayType, int address) {
return callFrame.getMemory(address + 8, 4).then(data -> {
if (data == null) {
return classToString(arrayType);
}
var length = readInt(data, 0);
return classToString(arrayType.elementType()) + "[" + length + "]";
});
}
private Promise decodeString(ClassLayout cls, int address) {
for (var field : cls.instanceFields()) {
if (field.name().equals("characters") && field.type() == FieldType.OBJECT) {
return callFrame.getMemory(address + field.address(), 4).thenAsync(data -> {
var charsAddress = readInt(data, 0);
return decodeChars(charsAddress);
});
}
}
return null;
}
private Promise decodeChars(int address) {
return callFrame.getMemory(address, 12).thenAsync(data -> {
if (data == null) {
return null;
}
var classPtr = readInt(data, 0) << 3;
var type = debugInfo.classLayoutInfo().find(classPtr);
if (!(type instanceof ArrayLayout)) {
return null;
}
var elementType = ((ArrayLayout) type).elementType();
if (!(elementType instanceof PrimitiveLayout)) {
return null;
}
var primitiveType = ((PrimitiveLayout) elementType).primitiveType();
if (primitiveType != PrimitiveType.CHARACTER) {
return null;
}
var length = readInt(data, 8);
return callFrame.getMemory(address + 12, length * 2).then(charsData -> {
if (charsData == null) {
return null;
}
var sb = new StringBuilder("\"");
for (var i = 0; i < length; ++i) {
appendChar(sb, (char) readShort(charsData, i * 2));
}
sb.append("\"");
return sb.toString();
});
});
}
private String decodeClass(int address) {
var type = debugInfo.classLayoutInfo().find(address);
return type != null ? classToString(type) : null;
}
@Override
Promise prepareType() {
switch (type) {
case BOOLEAN:
return Promise.of("boolean");
case BYTE:
return Promise.of("byte");
case SHORT:
return Promise.of("short");
case CHAR:
return Promise.of("char");
case INT:
return Promise.of("int");
case LONG:
return Promise.of("long");
case FLOAT:
return Promise.of("float");
case DOUBLE:
return Promise.of("double");
case ADDRESS:
return Promise.of("address");
case OBJECT:
return fetchObjectType();
default:
return Promise.of("undefined");
}
}
private Promise getCalculatedType() {
if (calculatedType == null) {
calculatedType = callFrame.getMemory((int) longValue, 4).then(data -> {
if (data == null) {
return null;
}
var header = readInt(data, 0);
var classPtr = header << 3;
var classes = debugInfo.classLayoutInfo();
if (classes == null) {
return null;
}
return classes.find(classPtr);
});
}
return calculatedType;
}
private Promise fetchObjectType() {
if (longValue == 0) {
return Promise.of("null");
}
return getCalculatedType().then(cls -> {
if (cls == null) {
return "error";
}
return classToString(cls);
});
}
private String classToString(TypeLayout type) {
switch (type.kind()) {
case PRIMITIVE:
switch (((PrimitiveLayout) type).primitiveType()) {
case BOOLEAN:
return "boolean";
case BYTE:
return "byte";
case SHORT:
return "short";
case CHARACTER:
return "char";
case INTEGER:
return "int";
case LONG:
return "long";
case FLOAT:
return "float";
case DOUBLE:
return "double";
default:
break;
}
break;
case CLASS:
return ((ClassLayout) type).classRef().fullName();
case INTERFACE:
return ((InterfaceLayout) type).classRef().fullName();
case ARRAY:
return classToString(((ArrayLayout) type).elementType()) + "[]";
default:
break;
}
return "unknown";
}
@Override
Promise
© 2015 - 2024 Weber Informatics LLC | Privacy Policy