/*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.sl.test;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.metadata.Scope;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.EventBinding;
import com.oracle.truffle.api.instrumentation.EventContext;
import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.instrumentation.TruffleInstrument;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.java.JavaInterop;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.vm.PolyglotEngine;
import com.oracle.truffle.api.vm.PolyglotRuntime;
import com.oracle.truffle.sl.SLLanguage;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.junit.Assert;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
/**
* Test of SL instrumentation.
*/
public class SLInstrumentTest {
@Test
public void testLexicalScopes() {
String code = "function test(n) {\n" +
" a = 1;\n" + // 2
" if (a > 0) {\n" +
" b = 10;\n" +
" println(b);\n" + // 5
" }\n" +
" if (a == 1) {\n" +
" b = 20;\n" +
" a = 0;\n" +
" c = 1;\n" + // 10
" if (b > 0) {\n" +
" a = 4;\n" +
" b = 5;\n" +
" c = 6;\n" +
" d = 7;\n" + // 15
" println(d);\n" +
" }\n" +
" }\n" +
" println(b);\n" +
" println(a);\n" + // 20
"}\n" +
"function main() {\n" +
" test(\"n_n\");\n" +
"}";
Source source = Source.newBuilder(code).name("testing").mimeType(SLLanguage.MIME_TYPE).build();
PolyglotEngine engine = PolyglotEngine.newBuilder().setOut(new java.io.OutputStream() {
// null output stream
@Override
public void write(int b) throws IOException {
}
}).build();
PolyglotRuntime.Instrument envInstr = engine.getRuntime().getInstruments().get("testEnvironmentHandlerInstrument");
envInstr.setEnabled(true);
TruffleInstrument.Env env = envInstr.lookup(Environment.class).env;
List<Throwable> throwables = new ArrayList<>();
env.getInstrumenter().attachListener(SourceSectionFilter.newBuilder().lineIn(1, source.getLineCount()).build(), new ExecutionEventListener() {
@Override
public void onEnter(EventContext context, VirtualFrame frame) {
Node node = context.getInstrumentedNode();
Iterable<Scope> lexicalScopes = Scope.findScopes(env, node, frame);
try {
verifyLexicalScopes(lexicalScopes, context.getInstrumentedSourceSection().getStartLine(), frame.materialize());
} catch (ThreadDeath t) {
throw t;
} catch (Throwable t) {
CompilerDirectives.transferToInterpreter();
PrintStream lsErr = System.err;
lsErr.println("Line = " + context.getInstrumentedSourceSection().getStartLine());
lsErr.println("Node = " + node + ", class = " + node.getClass().getName());
t.printStackTrace(lsErr);
throwables.add(t);
}
}
@Override
public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
}
@Override
public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
}
});
engine.eval(source);
Assert.assertTrue(throwables.toString(), throwables.isEmpty());
}
@CompilerDirectives.TruffleBoundary
private static void verifyLexicalScopes(Iterable<Scope> lexicalScopes, int line, MaterializedFrame frame) {
int depth = 0;
switch (line) {
case 1:
case 2:
for (Scope ls : lexicalScopes) {
// Test that ls.getNode() returns the current root node:
checkRootNode(ls, "test", frame);
TruffleObject arguments = (TruffleObject) ls.getArguments(null);
checkVars(arguments, "n", null);
arguments = (TruffleObject) ls.getArguments(frame);
checkVars(arguments, "n", "n_n");
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables, "n", null);
variables = (TruffleObject) ls.getVariables(frame);
checkVars(variables, "n", "n_n");
depth++;
}
assertEquals("LexicalScope depth", 1, depth);
break;
case 3:
case 7:
case 19:
case 20:
for (Scope ls : lexicalScopes) {
checkRootNode(ls, "test", frame);
TruffleObject arguments = (TruffleObject) ls.getArguments(null);
checkVars(arguments, "n", null);
arguments = (TruffleObject) ls.getArguments(frame);
checkVars(arguments, "n", "n_n");
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables, "n", null, "a", null);
variables = (TruffleObject) ls.getVariables(frame);
long aVal = (line < 19) ? 1L : 4L;
checkVars(variables, "n", "n_n", "a", aVal);
depth++;
}
assertEquals("LexicalScope depth", 1, depth);
break;
case 4:
case 8:
for (Scope ls : lexicalScopes) {
if (depth == 0) {
checkBlock(ls);
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables);
assertNull(ls.getArguments(null));
assertNull(ls.getArguments(frame));
} else {
checkRootNode(ls, "test", frame);
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables, "n", null, "a", null);
variables = (TruffleObject) ls.getVariables(frame);
checkVars(variables, "n", "n_n", "a", 1L);
TruffleObject arguments = (TruffleObject) ls.getArguments(null);
checkVars(arguments, "n", null);
arguments = (TruffleObject) ls.getArguments(frame);
checkVars(arguments, "n", "n_n");
}
depth++;
}
assertEquals("LexicalScope depth", 2, depth);
break;
case 5:
case 9:
case 10:
for (Scope ls : lexicalScopes) {
if (depth == 0) {
checkBlock(ls);
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables, "b", null);
variables = (TruffleObject) ls.getVariables(frame);
long bVal = (line == 5) ? 10L : 20L;
checkVars(variables, "b", bVal);
assertNull(ls.getArguments(null));
assertNull(ls.getArguments(frame));
} else {
checkRootNode(ls, "test", frame);
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables, "n", null, "a", null);
variables = (TruffleObject) ls.getVariables(frame);
long aVal = (line == 10) ? 0L : 1L;
checkVars(variables, "n", "n_n", "a", aVal);
TruffleObject arguments = (TruffleObject) ls.getArguments(null);
checkVars(arguments, "n", null);
arguments = (TruffleObject) ls.getArguments(frame);
checkVars(arguments, "n", "n_n");
}
depth++;
}
assertEquals("LexicalScope depth", 2, depth);
break;
case 11:
for (Scope ls : lexicalScopes) {
if (depth == 0) {
checkBlock(ls);
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables, "b", null, "c", null);
variables = (TruffleObject) ls.getVariables(frame);
checkVars(variables, "b", 20L, "c", 1L);
assertNull(ls.getArguments(null));
assertNull(ls.getArguments(frame));
} else {
checkRootNode(ls, "test", frame);
}
depth++;
}
assertEquals("LexicalScope depth", 2, depth);
break;
case 12:
case 13:
case 14:
case 15:
for (Scope ls : lexicalScopes) {
if (depth == 0) {
checkBlock(ls);
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables);
assertNull(ls.getArguments(null));
assertNull(ls.getArguments(frame));
} else if (depth == 1) {
checkBlock(ls);
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables, "b", null, "c", null);
variables = (TruffleObject) ls.getVariables(frame);
long bVal = (line < 14) ? 20L : 5L;
long cVal = (line < 15) ? 1L : 6L;
checkVars(variables, "b", bVal, "c", cVal);
assertNull(ls.getArguments(null));
assertNull(ls.getArguments(frame));
} else {
checkRootNode(ls, "test", frame);
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables, "n", null, "a", null);
variables = (TruffleObject) ls.getVariables(frame);
long aVal = (line == 12) ? 0L : 4L;
checkVars(variables, "n", "n_n", "a", aVal);
}
depth++;
}
assertEquals("LexicalScope depth", 3, depth);
break;
case 16:
for (Scope ls : lexicalScopes) {
if (depth == 0) {
checkBlock(ls);
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables, "d", null);
variables = (TruffleObject) ls.getVariables(frame);
checkVars(variables, "d", 7L);
assertNull(ls.getArguments(null));
assertNull(ls.getArguments(frame));
} else if (depth == 1) {
checkBlock(ls);
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables, "b", null, "c", null);
variables = (TruffleObject) ls.getVariables(frame);
checkVars(variables, "b", 5L, "c", 6L);
assertNull(ls.getArguments(null));
assertNull(ls.getArguments(frame));
} else {
checkRootNode(ls, "test", frame);
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables, "n", null, "a", null);
variables = (TruffleObject) ls.getVariables(frame);
checkVars(variables, "n", "n_n", "a", 4L);
}
depth++;
}
assertEquals("LexicalScope depth", 3, depth);
break;
case 22:
case 23:
for (Scope ls : lexicalScopes) {
checkRootNode(ls, "main", frame);
TruffleObject arguments = (TruffleObject) ls.getArguments(null);
checkVars(arguments);
TruffleObject variables = (TruffleObject) ls.getVariables(null);
checkVars(variables);
depth++;
}
assertEquals("LexicalScope depth", 1, depth);
break;
default:
fail("Untested line: " + line);
break;
}
}
private static void checkRootNode(Scope ls, String name, MaterializedFrame frame) {
assertEquals(name, ls.getName());
Node node = ls.getNode();
assertTrue(node.getClass().getName(), node instanceof RootNode);
assertEquals(name, ((RootNode) node).getName());
assertEquals(frame.getFrameDescriptor(), ((RootNode) node).getFrameDescriptor());
}
private static void checkBlock(Scope ls) {
assertEquals("block", ls.getName());
// Test that ls.getNode() does not return the current root node, it ought to be a block node
Node node = ls.getNode();
assertNotNull(node);
assertFalse(node.getClass().getName(), node instanceof RootNode);
}
@SuppressWarnings("rawtypes")
private static void checkVars(TruffleObject vars, Object... expected) {
Map map = JavaInterop.asJavaObject(Map.class, vars);
for (int i = 0; i < expected.length; i += 2) {
String name = (String) expected[i];
Object value = expected[i + 1];
assertTrue(name, map.containsKey(name));
if (value != null) {
assertEquals(name, value, map.get(name));
} else {
try {
map.get(name);
fail(name + " should not allow to read the value.");
} catch (Exception ex) {
if (ex instanceof UnsupportedMessageException) {
// O.K.
} else {
throw new RuntimeException(ex);
}
}
}
}
assertEquals(map.keySet().toString(), expected.length / 2, map.size());
}
@Test
public void testOutput() throws IOException {
String code = "function main() {\n" +
" f = fac(5);\n" +
" println(f);\n" +
"}\n" +
"function fac(n) {\n" +
" println(n);\n" +
" if (n <= 1) {\n" +
" return 1;\n" + // break
" }\n" +
" return n * fac(n - 1);\n" +
"}\n";
String fullOutput = "5\n4\n3\n2\n1\n120\n";
String fullLines = "[5, 4, 3, 2, 1, 120]";
// Pure exec:
Source source = Source.newBuilder(code).name("testing").mimeType(SLLanguage.MIME_TYPE).build();
ByteArrayOutputStream engineOut = new ByteArrayOutputStream();
PolyglotEngine engine = PolyglotEngine.newBuilder().setOut(engineOut).build();
engine.eval(source);
String engineOutput = fullOutput;
Assert.assertEquals(engineOutput, engineOut.toString());
// Check output
PolyglotRuntime.Instrument outInstr = engine.getRuntime().getInstruments().get("testEnvironmentHandlerInstrument");
outInstr.setEnabled(true);
TruffleInstrument.Env env = outInstr.lookup(Environment.class).env;
ByteArrayOutputStream consumedOut = new ByteArrayOutputStream();
EventBinding<ByteArrayOutputStream> outputConsumerBinding = env.getInstrumenter().attachOutConsumer(consumedOut);
Assert.assertEquals(0, consumedOut.size());
engine.eval(source);
BufferedReader fromOutReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(consumedOut.toByteArray())));
engineOutput = engineOutput + fullOutput;
Assert.assertEquals(engineOutput, engineOut.toString());
Assert.assertTrue(fromOutReader.ready());
Assert.assertEquals(fullLines, readLinesList(fromOutReader));
// Check two output readers
ByteArrayOutputStream consumedOut2 = new ByteArrayOutputStream();
EventBinding<ByteArrayOutputStream> outputConsumerBinding2 = env.getInstrumenter().attachOutConsumer(consumedOut2);
Assert.assertEquals(0, consumedOut2.size());
engine.eval(source);
fromOutReader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(consumedOut.toByteArray())));
BufferedReader fromOutReader2 = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(consumedOut2.toByteArray())));
engineOutput = engineOutput + fullOutput;
Assert.assertEquals(engineOutput, engineOut.toString());
Assert.assertTrue(fromOutReader.ready());
Assert.assertTrue(fromOutReader2.ready());
String fullLines2x = fullLines.substring(0, fullLines.length() - 1) + ", " + fullLines.substring(1);
Assert.assertEquals(fullLines2x, readLinesList(fromOutReader));
Assert.assertEquals(fullLines, readLinesList(fromOutReader2));
// One output reader closes, the other still receives the output
outputConsumerBinding.dispose();
consumedOut.reset();
consumedOut2.reset();
engine.eval(source);
engineOutput = engineOutput + fullOutput;
Assert.assertEquals(engineOutput, engineOut.toString());
Assert.assertEquals(0, consumedOut.size());
Assert.assertTrue(consumedOut2.size() > 0);
fromOutReader2 = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(consumedOut2.toByteArray())));
Assert.assertEquals(fullLines, readLinesList(fromOutReader2));
// Remaining closes and pure exec successful:
consumedOut2.reset();
outputConsumerBinding2.dispose();
engine.eval(source);
engineOutput = engineOutput + fullOutput;
Assert.assertEquals(engineOutput, engineOut.toString());
Assert.assertEquals(0, consumedOut.size());
Assert.assertEquals(0, consumedOut2.size());
// Add a reader again and disable the instrument:
env.getInstrumenter().attachOutConsumer(consumedOut);
outInstr.setEnabled(false);
engine.eval(source);
engineOutput = engineOutput + fullOutput;
Assert.assertEquals(engineOutput, engineOut.toString());
Assert.assertEquals(0, consumedOut.size());
Assert.assertEquals(0, consumedOut2.size());
}
String readLinesList(BufferedReader br) throws IOException {
List<String> lines = new ArrayList<>();
while (br.ready()) {
String line = br.readLine();
if (line == null) {
break;
}
lines.add(line);
}
return lines.toString();
}
@TruffleInstrument.Registration(id = "testEnvironmentHandlerInstrument")
public static class EnvironmentHandlerInstrument extends TruffleInstrument {
@Override
protected void onCreate(final TruffleInstrument.Env env) {
env.registerService(new Environment(env));
}
}
private static class Environment {
TruffleInstrument.Env env;
Environment(TruffleInstrument.Env env) {
this.env = env;
}
}
}