/* * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.oracle.truffle.api.debug; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Objects; import com.oracle.truffle.api.interop.KeyInfo; import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.java.JavaInterop; import com.oracle.truffle.api.nodes.LanguageInfo; import com.oracle.truffle.api.nodes.RootNode; import com.oracle.truffle.api.source.SourceSection; /** * Represents a value accessed using the debugger API. Please note that values can become invalid * depending on the context in which they are used. For example stack values will only remain valid * as long as the current stack element is active. Heap values on the other hand remain valid. If a * value becomes invalid then setting or getting a value will throw an {@link IllegalStateException} * . {@link DebugValue} instances neither support equality or preserve identity. * <p> * Clients may access the debug value only on the execution thread where the suspended event of the * stack frame was created and notification received; access from other threads will throw * {@link IllegalStateException}. * * @since 0.17 */ public abstract class DebugValue { abstract Object get(); DebugValue() { } /** * Sets the value using another {@link DebugValue}. Throws an {@link IllegalStateException} if * the value is not writable, the passed value is not readable, this value or the passed value * is invalid, or the guest language of the values do not match. Use * {@link DebugStackFrame#eval(String)} to evaluate values to be set. * * @param value the value to set * @since 0.17 */ public abstract void set(DebugValue value); /** * Converts the debug value into a Java type. Class conversions which are always supported: * <ul> * <li>{@link String}.class converts the value to its language specific string representation. * </li> * </ul> * No optional conversions are currently available. If a conversion is not supported then an * {@link UnsupportedOperationException} is thrown. If the value is not {@link #isReadable() * readable} then an {@link IllegalStateException} is thrown. * * @param clazz the type to convert to * @return the converted Java type * @since 0.17 */ public abstract <T> T as(Class<T> clazz); /** * Returns the name of this value as it is referred to from its origin. If this value is * originated from the stack it returns the name of the local variable. If the value was * returned from another objects then it returns the name of the property or field it is * contained in. If no name is available <code>null</code> is returned. * * @since 0.17 */ public abstract String getName(); /** * Returns <code>true</code> if this value can be read else <code>false</code>. * * @see #as(Class) * @since 0.17 */ public abstract boolean isReadable(); /** * Returns <code>true</code> if this value can be read else <code>false</code>. * * @see #as(Class) * @since 0.17 * @deprecated Use {@link #isWritable()} */ @Deprecated public final boolean isWriteable() { return isWritable(); } /** * Returns <code>true</code> if this value can be written to, else <code>false</code>. * * @see #as(Class) * @since 0.26 */ public abstract boolean isWritable(); /** * Returns <code>true</code> if this value represents an internal implementation detail, * <code>false</code> otherwise. Internal values should be hidden during normal guest language * debugging. * <p> * Language implementations sometimes create internal helper variables that do not correspond to * anything explicitly written by a programmer. Language implementors mark these variables as * <em>internal</em>. * </p> * <p> * Clients of the debugging API should assume that displaying <em>internal</em> values is * unlikely to help programmers debug guest language programs and might possibly create * confusion. However, clients may choose to display all values, for example in a special mode * to support development of programming language implementations. * </p> * * @since 0.26 */ public abstract boolean isInternal(); /** * Get the scope where this value is declared in. It returns a non-null value for local * variables declared on a stack. It's <code>null<code> for object properties and other heap * values. * * @return the scope, or <code>null</code> when this value does not belong into any scope. * * @since 0.26 */ public DebugScope getScope() { return null; } /** * Provides properties representing an internal structure of this value. The returned collection * is not thread-safe. If the value is not {@link #isReadable() readable} then an * {@link IllegalStateException} is thrown. * * @return a collection of property values, or </code>null</code> when the value does not have * any concept of properties. * @since 0.19 */ public final Collection<DebugValue> getProperties() { if (!isReadable()) { throw new IllegalStateException("Value is not readable"); } Object value = get(); return getProperties(value, getDebugger(), getSourceRoot(), null); } static ValuePropertiesCollection getProperties(Object value, Debugger debugger, RootNode root, DebugScope scope) { ValuePropertiesCollection properties = null; if (value instanceof TruffleObject) { TruffleObject object = (TruffleObject) value; Map<Object, Object> map = ObjectStructures.asMap(debugger.getMessageNodes(), object); if (map != null) { properties = new ValuePropertiesCollection(debugger, root, object, map, map.entrySet(), scope); } } return properties; } /* * TODO future API: Find a property value based on a String name. In general, not all properties * may have String names. Use this for lookup of a value of some known String-based property. * DebugValue findProperty(String name) */ /** * Returns <code>true</code> if this value represents an array, <code>false</code> otherwise. * * @since 0.19 */ public final boolean isArray() { Object value = get(); if (value instanceof TruffleObject) { TruffleObject to = (TruffleObject) value; return ObjectStructures.isArray(getDebugger().getMessageNodes(), to); } else { return false; } } /** * Provides array elements when this value represents an array. To test if this value represents * an array, check {@link #isArray()}. * * @return a list of array elements, or <code>null</code> when the value does not represent an * array. * @since 0.19 */ public final List<DebugValue> getArray() { List<DebugValue> arrayList = null; Object value = get(); if (value instanceof TruffleObject) { TruffleObject to = (TruffleObject) value; List<Object> array = ObjectStructures.asList(getDebugger().getMessageNodes(), to); if (array != null) { arrayList = new ValueInteropList(getDebugger(), getSourceRoot(), array); } } return arrayList; } /** * Get a meta-object of this value, if any. The meta-object represents a description of the * value, reveals it's kind and it's features. * * @return a value representing the meta-object, or <code>null</code> * @since 0.22 */ public final DebugValue getMetaObject() { Object obj = get(); if (obj == null) { return null; } obj = getDebugger().getEnv().findMetaObject(getSourceRoot(), obj); if (obj == null) { return null; } else { return new HeapValue(getDebugger(), getSourceRoot(), obj); } } /** * Get a source location where this value is declared, if any. * * @return a source location of the object, or <code>null</code> * @since 0.22 */ public final SourceSection getSourceLocation() { Object obj = get(); if (obj == null) { return null; } return getDebugger().getEnv().findSourceLocation(getSourceRoot(), obj); } abstract Debugger getDebugger(); abstract RootNode getSourceRoot(); final LanguageInfo getLanguageInfo() { RootNode root = getSourceRoot(); if (root != null) { return root.getLanguageInfo(); } return null; } /** * Returns a string representation of the debug value. * * @since 0.17 */ @Override public String toString() { return "DebugValue(name=" + getName() + ", value = " + as(String.class) + ")"; } static class HeapValue extends DebugValue { // identifies the debugger and engine private final Debugger debugger; // identifiers the original root this value originates from private final RootNode sourceRoot; private final Object value; HeapValue(Debugger debugger, RootNode root, Object value) { this.debugger = debugger; this.sourceRoot = root; this.value = value; } @SuppressWarnings("unchecked") @Override public <T> T as(Class<T> clazz) { if (!isReadable()) { throw new IllegalStateException("Value is not readable"); } if (clazz == String.class) { Object val = get(); String stringValue; if (sourceRoot == null) { stringValue = val.toString(); } else { stringValue = debugger.getEnv().toString(sourceRoot, val); } return (T) stringValue; } throw new UnsupportedOperationException(); } @Override Object get() { return value; } @Override public void set(DebugValue expression) { throw new IllegalStateException("Value is not writable"); } @Override public String getName() { return null; } @Override public boolean isReadable() { return true; } @Override public boolean isWritable() { return false; } @Override public boolean isInternal() { return false; } @Override Debugger getDebugger() { return debugger; } @Override RootNode getSourceRoot() { return sourceRoot; } } static final class PropertyValue extends HeapValue { private final int keyInfo; private final Map.Entry<Object, Object> property; private final DebugScope scope; PropertyValue(Debugger debugger, RootNode root, TruffleObject object, Map.Entry<Object, Object> property, DebugScope scope) { super(debugger, root, null); this.keyInfo = JavaInterop.getKeyInfo(object, property.getKey()); this.property = property; this.scope = scope; } @Override Object get() { checkValid(); return property.getValue(); } @Override public String getName() { checkValid(); String name; RootNode sourceRoot = getSourceRoot(); Object propertyKey = property.getKey(); if (propertyKey instanceof String) { name = (String) propertyKey; } else { if (sourceRoot == null) { name = Objects.toString(propertyKey); } else { name = getDebugger().getEnv().toString(sourceRoot, propertyKey); } } return name; } @Override public boolean isReadable() { checkValid(); return KeyInfo.isReadable(keyInfo); } @Override public boolean isWritable() { checkValid(); return KeyInfo.isWritable(keyInfo); } @Override public boolean isInternal() { checkValid(); return KeyInfo.isInternal(keyInfo); } @Override public DebugScope getScope() { checkValid(); return scope; } @Override public void set(DebugValue value) { checkValid(); property.setValue(value.get()); } private void checkValid() { if (scope != null) { scope.verifyValidState(); } } } static final class PropertyNamedValue extends HeapValue { private final int keyInfo; private final Map<Object, Object> map; private final String name; private final DebugScope scope; PropertyNamedValue(Debugger debugger, RootNode root, TruffleObject object, Map<Object, Object> map, String name, DebugScope scope) { super(debugger, root, null); this.keyInfo = JavaInterop.getKeyInfo(object, name); this.map = map; this.name = name; this.scope = scope; } @Override public String getName() { checkValid(); return name; } @Override Object get() { checkValid(); return map.get(name); } @Override public DebugScope getScope() { checkValid(); return scope; } @Override public boolean isReadable() { checkValid(); return KeyInfo.isReadable(keyInfo); } @Override public boolean isWritable() { checkValid(); return KeyInfo.isWritable(keyInfo); } @Override public boolean isInternal() { checkValid(); return KeyInfo.isInternal(keyInfo); } @Override public void set(DebugValue value) { checkValid(); map.put(name, value.get()); } private void checkValid() { if (scope != null) { scope.verifyValidState(); } } } }