| // Copyright 2018 The Bazel Authors. All rights reserved. |
| // |
| // 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 com.google.devtools.build.lib.starlarkdebug.server; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Ordering; |
| import com.google.devtools.build.lib.starlarkdebugging.StarlarkDebuggingProtos; |
| import com.google.devtools.build.lib.starlarkdebugging.StarlarkDebuggingProtos.Value; |
| import java.lang.reflect.Array; |
| import java.util.Map; |
| import java.util.Set; |
| import net.starlark.java.eval.Debug; |
| import net.starlark.java.eval.EvalException; |
| import net.starlark.java.eval.Starlark; |
| import net.starlark.java.eval.StarlarkInt; |
| import net.starlark.java.eval.StarlarkSemantics; |
| import net.starlark.java.eval.StarlarkValue; |
| import net.starlark.java.eval.Structure; |
| |
| /** Helper class for creating {@link StarlarkDebuggingProtos.Value} from Starlark objects. */ |
| final class DebuggerSerialization { |
| |
| static Value getValueProto(ThreadObjectMap objectMap, String label, Object value) { |
| // TODO(bazel-team): prune cycles, and provide a way to limit breadth/depth of children reported |
| boolean hasChildren = hasChildren(value); |
| return Value.newBuilder() |
| .setLabel(label) |
| // TODO(bazel-team): omit type details for non-Starlark values |
| .setType(Starlark.type(value)) |
| .setDescription(getDescription(value)) |
| .setHasChildren(hasChildren) |
| .setId(hasChildren ? objectMap.registerValue(value) : 0) |
| .build(); |
| } |
| |
| private static String getDescription(Object value) { |
| if (value instanceof String) { |
| return (String) value; |
| } |
| return Starlark.repr(value); |
| } |
| |
| private static boolean hasChildren(Object value) { |
| if (value instanceof Map) { |
| return !((Map) value).isEmpty(); |
| } |
| if (value instanceof Map.Entry) { |
| return true; |
| } |
| if (value instanceof Iterable) { |
| return ((Iterable) value).iterator().hasNext(); |
| } |
| if (value.getClass().isArray()) { |
| return Array.getLength(value) > 0; |
| } |
| if (value instanceof Debug.ValueWithDebugAttributes) { |
| return true; |
| } |
| if (value instanceof StarlarkInt) { |
| return false; |
| } |
| if (value instanceof Structure || value instanceof StarlarkValue) { |
| // assuming Structure's have at least one child as a temporary optimization |
| // TODO(bazel-team): remove once child-listing logic is moved to StarlarkValue |
| return true; |
| } |
| // fallback to assuming there are no children |
| return false; |
| } |
| |
| static ImmutableList<Value> getChildren(ThreadObjectMap objectMap, Object value) { |
| if (value instanceof Map) { |
| return getChildren(objectMap, ((Map) value).entrySet()); |
| } |
| if (value instanceof Map.Entry) { |
| return getChildren(objectMap, (Map.Entry) value); |
| } |
| if (value instanceof Iterable) { |
| return getChildren(objectMap, (Iterable) value); |
| } |
| if (value.getClass().isArray()) { |
| return getArrayChildren(objectMap, value); |
| } |
| if (value instanceof Debug.ValueWithDebugAttributes) { |
| return getDebugAttributes(objectMap, (Debug.ValueWithDebugAttributes) value); |
| } |
| // TODO(bazel-team): move child-listing logic to StarlarkValue where practical |
| if (value instanceof Structure) { |
| return getChildren(objectMap, (Structure) value); |
| } |
| if (value instanceof StarlarkValue) { |
| return getChildren(objectMap, (StarlarkValue) value); |
| } |
| // fallback to assuming there are no children |
| return ImmutableList.of(); |
| } |
| |
| private static ImmutableList<Value> getChildren( |
| ThreadObjectMap objectMap, Structure classObject) { |
| ImmutableList.Builder<Value> builder = ImmutableList.builder(); |
| for (String key : Ordering.natural().immutableSortedCopy(classObject.getFieldNames())) { |
| try { |
| Object value = classObject.getValue(key); |
| if (value != null) { |
| builder.add(getValueProto(objectMap, key, value)); |
| } |
| } catch (EvalException | IllegalArgumentException e) { |
| // silently ignore errors |
| } |
| } |
| return builder.build(); |
| } |
| |
| private static ImmutableList<Value> getChildren( |
| ThreadObjectMap objectMap, StarlarkValue starlarkValue) { |
| StarlarkSemantics semantics = StarlarkSemantics.DEFAULT; // TODO(adonovan): obtain from thread. |
| // TODO(adonovan): would the debugger be content with Starlark.{dir,getattr} |
| // instead of getAnnotatedField{,Names}, if we filtered out BuiltinCallables? |
| Set<String> fieldNames; |
| try { |
| fieldNames = Starlark.getAnnotatedFieldNames(semantics, starlarkValue); |
| } catch (IllegalArgumentException e) { |
| // silently return no children |
| return ImmutableList.of(); |
| } |
| ImmutableList.Builder<Value> children = ImmutableList.builder(); |
| for (String fieldName : fieldNames) { |
| try { |
| children.add( |
| getValueProto( |
| objectMap, |
| fieldName, |
| Starlark.getAnnotatedField(semantics, starlarkValue, fieldName))); |
| } catch (EvalException | InterruptedException | IllegalArgumentException e) { |
| // silently ignore errors |
| } |
| } |
| return children.build(); |
| } |
| |
| private static ImmutableList<Value> getChildren( |
| ThreadObjectMap objectMap, Map.Entry<?, ?> entry) { |
| return ImmutableList.of( |
| getValueProto(objectMap, "key", entry.getKey()), |
| getValueProto(objectMap, "value", entry.getValue())); |
| } |
| |
| private static ImmutableList<Value> getChildren(ThreadObjectMap objectMap, Iterable<?> iterable) { |
| ImmutableList.Builder<Value> builder = ImmutableList.builder(); |
| int index = 0; |
| for (Object value : iterable) { |
| builder.add(getValueProto(objectMap, String.format("[%d]", index++), value)); |
| } |
| return builder.build(); |
| } |
| |
| private static ImmutableList<Value> getDebugAttributes( |
| ThreadObjectMap objectMap, Debug.ValueWithDebugAttributes value) { |
| ImmutableList.Builder<Value> attributes = ImmutableList.builder(); |
| for (Debug.DebugAttribute attr : value.getDebugAttributes()) { |
| attributes.add(getValueProto(objectMap, attr.name, attr.value)); |
| } |
| return attributes.build(); |
| } |
| |
| private static ImmutableList<Value> getArrayChildren(ThreadObjectMap objectMap, Object array) { |
| ImmutableList.Builder<Value> builder = ImmutableList.builder(); |
| int index = 0; |
| for (int i = 0; i < Array.getLength(array); i++) { |
| builder.add(getValueProto(objectMap, String.format("[%d]", index++), Array.get(array, i))); |
| } |
| return builder.build(); |
| } |
| } |