package com.oracle.truffle.tools.chromeinspector.test;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.GenerateWrapper;
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
import com.oracle.truffle.api.instrumentation.ProbeNode;
import com.oracle.truffle.api.instrumentation.StandardTags;
import com.oracle.truffle.api.instrumentation.Tag;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.test.polyglot.ProxyInteropObject;
import com.oracle.truffle.api.test.polyglot.ProxyLanguage;
import org.graalvm.polyglot.Source;
public class NotReadableValuesTest {
@Test
public void testNotReadableVariables() throws Exception {
InspectorTester tester = InspectorTester.start(true);
tester.sendMessage("{\"id\":1,\"method\":\"Runtime.enable\"}");
tester.sendMessage("{\"id\":2,\"method\":\"Debugger.enable\"}");
assertTrue(tester.compareReceivedMessages(
"{\"result\":{},\"id\":1}\n" +
"{\"result\":{},\"id\":2}\n"));
tester.sendMessage("{\"id\":3,\"method\":\"Runtime.runIfWaitingForDebugger\"}");
assertTrue(tester.compareReceivedMessages(
"{\"result\":{},\"id\":3}\n" +
"{\"method\":\"Runtime.executionContextCreated\",\"params\":{\"context\":{\"origin\":\"\",\"name\":\"test\",\"id\":1}}}\n"));
ProxyLanguage.setDelegate(new NotReadableValuesLanguage());
Source source = Source.newBuilder(ProxyLanguage.ID, "1", "ReadWithSideEffects.test").build();
String sourceURI = InspectorTester.getStringURI(source.getURI());
tester.eval(source);
assertNotNull(tester.receiveMessages("{\"method\":\"Debugger.scriptParsed\"", "}\n"));
assertTrue(tester.compareReceivedMessages(
"{\"method\":\"Debugger.paused\",\"params\":{\"reason\":\"other\",\"hitBreakpoints\":[]," +
"\"callFrames\":[{\"callFrameId\":\"0\",\"functionName\":\"TestRootNode\"," +
"\"scopeChain\":[{\"name\":\"TestRootNode\",\"type\":\"local\",\"object\":{\"description\":\"TestRootNode\",\"type\":\"object\",\"objectId\":\"1\"}}," +
"{\"name\":\"top\",\"type\":\"global\",\"object\":{\"description\":\"top\",\"type\":\"object\",\"objectId\":\"2\"}}]," +
"\"this\":{\"subtype\":\"null\",\"description\":\"null\",\"type\":\"object\",\"objectId\":\"3\"}," +
"\"functionLocation\":{\"scriptId\":\"0\",\"columnNumber\":0,\"lineNumber\":0}," +
"\"location\":{\"scriptId\":\"0\",\"columnNumber\":0,\"lineNumber\":0}," +
"\"url\":\"" + sourceURI + "\"" +
"}]}}\n"));
tester.sendMessage("{\"id\":10,\"method\":\"Runtime.getProperties\",\"params\":{\"objectId\":\"2\"}}");
assertTrue(tester.compareReceivedMessages(
"{\"result\":{\"result\":[{\"isOwn\":true,\"enumerable\":true,\"name\":\"object\"," +
"\"value\":{\"description\":\"Object VariablesObject\",\"className\":\"Object\",\"type\":\"object\",\"objectId\":\"4\"},\"configurable\":true,\"writable\":false}," +
"{\"isOwn\":true,\"enumerable\":true,\"name\":\"array\"," +
"\"value\":{\"subtype\":\"array\",\"description\":\"Object ArrayValue\",\"className\":\"Object\",\"type\":\"object\",\"objectId\":\"5\"},\"configurable\":true,\"writable\":false}]," +
"\"internalProperties\":[]},\"id\":10}\n"));
tester.sendMessage("{\"id\":20,\"method\":\"Runtime.getProperties\",\"params\":{\"objectId\":\"4\"}}");
assertTrue(tester.compareReceivedMessages(
"{\"result\":{\"result\":[{\"isOwn\":true,\"enumerable\":true,\"name\":\"object\"," +
"\"value\":{\"description\":\"Object VariablesObject\",\"className\":\"Object\",\"type\":\"object\",\"objectId\":\"6\"},\"configurable\":true,\"writable\":false}," +
"{\"isOwn\":true,\"enumerable\":true,\"name\":\"array\"," +
"\"value\":{\"subtype\":\"array\",\"description\":\"Object ArrayValue\",\"className\":\"Object\",\"type\":\"object\",\"objectId\":\"7\"},\"configurable\":true,\"writable\":false}]," +
"\"internalProperties\":[]},\"id\":20}\n"));
tester.sendMessage("{\"id\":30,\"method\":\"Runtime.getProperties\",\"params\":{\"objectId\":\"5\"}}");
assertTrue(tester.compareReceivedMessages(
"{\"result\":{\"result\":[{\"isOwn\":true,\"enumerable\":true,\"name\":\"0\",\"value\":{\"description\":\"6\",\"type\":\"number\",\"value\":6},\"configurable\":true,\"writable\":false}," +
"{\"isOwn\":true,\"enumerable\":true,\"name\":\"1\",\"value\":{\"description\":\"5\",\"type\":\"number\",\"value\":5},\"configurable\":true,\"writable\":false}," +
"{\"isOwn\":true,\"enumerable\":true,\"name\":\"4\",\"value\":{\"description\":\"2\",\"type\":\"number\",\"value\":2},\"configurable\":true,\"writable\":false}," +
"{\"isOwn\":true,\"enumerable\":true,\"name\":\"5\",\"value\":{\"description\":\"1\",\"type\":\"number\",\"value\":1},\"configurable\":true,\"writable\":false}]," +
"\"internalProperties\":[]},\"id\":30}\n"));
tester.sendMessage("{\"id\":100,\"method\":\"Debugger.resume\"}");
assertTrue(tester.compareReceivedMessages(
"{\"result\":{},\"id\":100}\n" +
"{\"method\":\"Debugger.resumed\"}\n"));
ProxyLanguage.setDelegate(new ProxyLanguage());
tester.finish();
}
static class NotReadableValuesLanguage extends ProxyLanguage {
@Override
protected final CallTarget parse(TruffleLanguage.ParsingRequest request) throws Exception {
return Truffle.getRuntime().createCallTarget(new TestRootNode(languageInstance, request.getSource()));
}
@Override
protected Object getScope(LanguageContext context) {
return new VariablesObject(true);
}
final class TestRootNode extends RootNode {
@Node.Child private NrStatementNode statement;
private final SourceSection statementSection;
TestRootNode(TruffleLanguage<?> language, com.oracle.truffle.api.source.Source source) {
super(language);
statementSection = source.createSection(1);
statement = new NrStatementNode(statementSection);
insert(statement);
}
@Override
public String getName() {
return TestRootNode.class.getSimpleName();
}
@Override
public SourceSection getSourceSection() {
return statementSection;
}
@Override
public Object execute(VirtualFrame frame) {
return statement.execute(frame);
}
@Override
protected boolean isInstrumentable() {
return true;
}
}
@GenerateWrapper
static class NrStatementNode extends Node implements InstrumentableNode {
private final SourceSection sourceSection;
NrStatementNode(SourceSection sourceSection) {
this.sourceSection = sourceSection;
}
@Override
public boolean isInstrumentable() {
return true;
}
@Override
public InstrumentableNode.WrapperNode createWrapper(ProbeNode probe) {
return new NrStatementNodeWrapper(sourceSection, this, probe);
}
public Object execute(VirtualFrame frame) {
assert frame != null;
return 10;
}
@Override
public SourceSection getSourceSection() {
return sourceSection;
}
@Override
public boolean hasTag(Class<? extends Tag> tag) {
return StandardTags.StatementTag.class.equals(tag);
}
}
private static final class VariablesObject extends ProxyInteropObject {
private static final String[] NAMES = new String[]{"nr_a", "object", "nr_object", "array", "nr_array"};
private final boolean isScope;
VariablesObject(boolean isScope) {
this.isScope = isScope;
}
@Override
protected boolean hasLanguage() {
return true;
}
@Override
protected boolean isScope() {
return isScope;
}
@Override
protected boolean hasMembers() {
return true;
}
@Override
@CompilerDirectives.TruffleBoundary
protected Object getMembers(boolean includeInternal) throws UnsupportedMessageException {
return new Names(NAMES);
}
@Override
protected boolean isMemberReadable(String member) {
return !member.startsWith("nr");
}
@Override
@CompilerDirectives.TruffleBoundary
protected Object readMember(String member) throws UnsupportedMessageException, UnknownIdentifierException {
if (member.startsWith("nr")) {
throw UnsupportedMessageException.create();
}
switch (member) {
case "object":
return new VariablesObject(false);
case "array":
return new ArrayValue();
default:
throw UnsupportedMessageException.create();
}
}
@Override
protected boolean hasMetaObject() {
return true;
}
@Override
protected Object getMetaObject() throws UnsupportedMessageException {
return new MetaObject();
}
@Override
protected Object toDisplayString(boolean allowSideEffects) {
if (isScope) {
return "top";
} else {
return VariablesObject.class.getSimpleName();
}
}
}
private static class MetaObject extends ProxyInteropObject {
@Override
protected boolean isMetaObject() {
return true;
}
@Override
protected boolean isMetaInstance(Object instance) {
return instance instanceof VariablesObject || instance instanceof ArrayValue;
}
@Override
protected String getMetaSimpleName() throws UnsupportedMessageException {
return "Object";
}
@Override
protected String getMetaQualifiedName() throws UnsupportedMessageException {
return "Object";
}
}
private static class Names extends ProxyInteropObject {
private final String[] names;
Names(String[] names) {
this.names = names;
}
@Override
protected boolean hasArrayElements() {
return true;
}
@Override
protected long getArraySize() throws UnsupportedMessageException {
return names.length;
}
@Override
protected boolean isArrayElementReadable(long index) {
return index >= 0 && index < names.length;
}
@Override
protected Object readArrayElement(long index) throws UnsupportedMessageException, InvalidArrayIndexException {
if (index >= 0 && index < names.length) {
return names[(int) index];
} else {
CompilerDirectives.transferToInterpreter();
throw InvalidArrayIndexException.create(index);
}
}
}
private static class ArrayValue extends ProxyInteropObject {
private static final long LENGTH = 6;
private static final Set<Long> NOT_READABLE = new HashSet<>(Arrays.asList(new Long[]{2L, 3L}));
@Override
protected boolean hasLanguage() {
return true;
}
@Override
protected boolean hasArrayElements() {
return true;
}
@Override
protected long getArraySize() throws UnsupportedMessageException {
return LENGTH;
}
@Override
@CompilerDirectives.TruffleBoundary
protected boolean isArrayElementReadable(long index) {
return index >= 0 && index < LENGTH && !NOT_READABLE.contains(index);
}
@Override
protected Object readArrayElement(long index) throws UnsupportedMessageException, InvalidArrayIndexException {
if (isArrayElementReadable(index)) {
return LENGTH - index;
} else {
CompilerDirectives.transferToInterpreter();
throw InvalidArrayIndexException.create(index);
}
}
@Override
protected boolean hasMetaObject() {
return true;
}
@Override
protected Object getMetaObject() throws UnsupportedMessageException {
return new MetaObject();
}
@Override
protected Object toDisplayString(boolean allowSideEffects) {
return ArrayValue.class.getSimpleName();
}
}
}
}