/******************************************************************************* * Copyright (c) 2016 Rogue Wave Software, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Rogue Wave Software, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.che.plugin.zdb.server.expressions; import static org.eclipse.che.plugin.zdb.server.expressions.IDbgDataFacet.Facet.KIND_ARRAY_MEMBER; import static org.eclipse.che.plugin.zdb.server.expressions.IDbgDataFacet.Facet.KIND_OBJECT_MEMBER; import static org.eclipse.che.plugin.zdb.server.expressions.IDbgDataFacet.Facet.MOD_PRIVATE; import static org.eclipse.che.plugin.zdb.server.expressions.IDbgDataFacet.Facet.MOD_PROTECTED; import static org.eclipse.che.plugin.zdb.server.expressions.IDbgDataFacet.Facet.MOD_PUBLIC; import static org.eclipse.che.plugin.zdb.server.expressions.IDbgDataType.DataType.PHP_ARRAY; import static org.eclipse.che.plugin.zdb.server.expressions.IDbgDataType.DataType.PHP_BOOL; import static org.eclipse.che.plugin.zdb.server.expressions.IDbgDataType.DataType.PHP_FLOAT; import static org.eclipse.che.plugin.zdb.server.expressions.IDbgDataType.DataType.PHP_INT; import static org.eclipse.che.plugin.zdb.server.expressions.IDbgDataType.DataType.PHP_OBJECT; import static org.eclipse.che.plugin.zdb.server.expressions.IDbgDataType.DataType.PHP_RESOURCE; import static org.eclipse.che.plugin.zdb.server.expressions.IDbgDataType.DataType.PHP_STRING; import java.io.ByteArrayInputStream; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.che.plugin.zdb.server.ZendDebugger; import org.eclipse.che.plugin.zdb.server.connection.IDbgMessage; import org.eclipse.che.plugin.zdb.server.connection.ZendDbgClientMessages.AssignValueRequest; import org.eclipse.che.plugin.zdb.server.connection.ZendDbgClientMessages.GetVariableValueRequest; import org.eclipse.che.plugin.zdb.server.connection.ZendDbgConnection; import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.AssignValueResponse; import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.GetVariableValueResponse; import org.eclipse.che.plugin.zdb.server.connection.ZendDbgEngineMessages.IDbgEngineResponse; import org.eclipse.che.plugin.zdb.server.expressions.IDbgDataFacet.Facet; /** * Zend debug expressions manager. * * @author Bartlomiej Laczkowski */ public class ZendDbgExpressionEvaluator { private static class ValueDecoder { public void deserialize(ZendDbgExpression expression, byte[] value) { if (value == null) { // Expression is illegal. value = new byte[] { 'N' }; } read(expression, new ValueReader(value)); } private void read(ZendDbgExpression expression, ValueReader reader) { char type = reader.readType(); switch (type) { case 'i': { readIntType(expression, reader); break; } case 'd': { readFloatType(expression, reader); break; } case 's': { readSringType(expression, reader); break; } case 'b': { readBooleanType(expression, reader); break; } case 'r': { readResourceType(expression, reader); break; } case 'a': { readArrayType(expression, reader); break; } case 'O': { readObjectType(expression, reader); break; } default: break; } } private void readIntType(ZendDbgExpression expression, ValueReader reader) { String value = reader.readToken(); expression.setExpressionResult(new ZendDbgExpressionResult(value, PHP_INT)); } private void readFloatType(ZendDbgExpression expression, ValueReader reader) { String value = reader.readToken(); expression.setExpressionResult(new ZendDbgExpressionResult(value, PHP_FLOAT)); } private void readSringType(ZendDbgExpression expression, ValueReader reader) { String value = reader.readString(); expression.setExpressionResult(new ZendDbgExpressionResult(value, PHP_STRING)); } private void readBooleanType(ZendDbgExpression expression, ValueReader reader) { String value = reader.readToken(); expression.setExpressionResult(new ZendDbgExpressionResult(value, PHP_BOOL)); } private void readResourceType(ZendDbgExpression expression, ValueReader reader) { // Read resource number and move on... reader.readInt(); reader.readInt(); String value = reader.readToken(); expression.setExpressionResult(new ZendDbgExpressionResult(value, PHP_RESOURCE)); } private void readArrayType(ZendDbgExpression expression, ValueReader reader) { int arrayLength = reader.readInt(); String arrayDescriptor = "array [" + arrayLength + "]"; if (reader.isEnd()) { expression.setExpressionResult(new ZendDbgExpressionResult(arrayDescriptor, PHP_ARRAY, arrayLength)); return; } List<IDbgExpression> childExpressions = new ArrayList<>(); for (int i = 0; i < arrayLength; i++) { char type = reader.readType(); String name; if (type == 'i') { name = Integer.toString(reader.readInt()); } else if (type == 's') { name = reader.readString(); } else { // Fall back when type is invalid return; } ZendDbgExpression childExpression = expression.createChild(name, KIND_ARRAY_MEMBER); childExpressions.add(childExpression); read(childExpression, reader); } expression.setExpressionResult( new ZendDbgExpressionResult(arrayDescriptor, PHP_OBJECT, arrayLength, childExpressions)); } private void readObjectType(ZendDbgExpression expression, ValueReader reader) { String className = reader.readString(); int objectLength = reader.readInt(); if (reader.isEnd()) { expression.setExpressionResult(new ZendDbgExpressionResult(className, PHP_OBJECT, objectLength)); return; } List<IDbgExpression> childExpressions = new ArrayList<>(); for (int i = 0; i < objectLength; i++) { char type = reader.readType(); String name; if (type == 'i') { name = Integer.toString(reader.readInt()); } else if (type == 's') { name = reader.readString(); } else { // Fall back when type is invalid return; } ZendDbgExpression childExpression; Facet fieldFacet = MOD_PUBLIC; if (name.startsWith("*::")) { fieldFacet = MOD_PROTECTED; } else if (name.contains("::")) { fieldFacet = MOD_PRIVATE; } childExpression = expression.createChild(name, KIND_OBJECT_MEMBER, fieldFacet); childExpressions.add(childExpression); read(childExpression, reader); } expression.setExpressionResult( new ZendDbgExpressionResult(className, PHP_OBJECT, objectLength, childExpressions)); } } private static class ValueReader extends ByteArrayInputStream { private ValueReader(byte[] result) { super(result); } char readType() { char curr; do { int temp = super.read(); if (temp == -1) { return ' '; } curr = (char) temp; } while (curr == ';' || curr == ':' || curr == '{' || curr == '}'); return curr; } String readToken() { StringBuffer buffer = new StringBuffer(6); char curr; do { curr = (char) super.read(); } while (curr == ';' || curr == ':'); while (curr != ';' && curr != ':') { buffer.append(curr); curr = (char) super.read(); } return buffer.toString(); } String readString() { int length = readInt(); while ((char) super.read() != '"') ; byte[] bytes = new byte[length]; read(bytes, 0, length); super.read(); // read '"' return getText(bytes); } int readInt() { int result = 0; char curr; boolean isMinus = false; do { curr = (char) super.read(); if (curr == '-') { isMinus = true; } } while (!Character.isDigit(curr)); do { result *= 10; result += Character.getNumericValue(curr); this.mark(1); } while (Character.isDigit(curr = (char) super.read())); if (isMinus) { result *= -1; } return result; } boolean isEnd() { this.reset(); char curr = (char) super.read(); return curr == ';'; } String getText(byte[] buf) { try { return new String(buf, IDbgMessage.ENCODING); } catch (UnsupportedEncodingException e) { } return new String(buf, Charset.defaultCharset()); } } private ZendDbgConnection debugConnection; private ValueDecoder valueDecoder; public ZendDbgExpressionEvaluator(ZendDbgConnection debugConnection) { this.debugConnection = debugConnection; this.valueDecoder = new ValueDecoder(); } public void evaluate(ZendDbgExpression expression, int depth) { byte[] value = requestEvaluation(expression, depth); valueDecoder.deserialize(expression, value); } public boolean assign(ZendDbgExpression expression, String newValue, int depth) { if (!requestAssignment(expression, newValue, depth)) { ZendDebugger.LOG.error("Could not assign new value for: " + expression.getExpression() + " variable."); return false; } return true; } private byte[] requestEvaluation(ZendDbgExpression expression, int depth) { String variableOwner = expression.getExpression(); List<String> variableElementPath = Collections.emptyList(); List<String> chain = expression.getExpressionChain(); if (!chain.isEmpty()) { variableOwner = chain.get(0); variableElementPath = chain.subList(1, chain.size()); } byte[] value = null; GetVariableValueResponse response = debugConnection .sendRequest(new GetVariableValueRequest(variableOwner, depth, variableElementPath)); if (isOK(response)) { value = response.getVariableValue(); } if (value == null) { value = new byte[] { 'N' }; } return value; } private boolean requestAssignment(ZendDbgExpression expression, String newValue, int depth) { List<String> path = expression.getExpressionChain(); String variableOwner = path.get(0); List<String> variableElementPath = path.subList(1, path.size()); AssignValueResponse response = debugConnection .sendRequest(new AssignValueRequest(variableOwner, newValue, depth, variableElementPath)); return isOK(response); } private boolean isOK(IDbgEngineResponse response) { return response != null && response.getStatus() == 0; } }