package com.oracle.truffle.js.test.instrumentation;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
import org.graalvm.polyglot.Source;
import org.junit.Assert;
import org.junit.Test;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.EventContext;
import com.oracle.truffle.api.instrumentation.ExecutionEventListener;
import com.oracle.truffle.api.instrumentation.InstrumentableNode;
import com.oracle.truffle.api.instrumentation.SourceSectionFilter;
import com.oracle.truffle.api.nodes.LoopNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeUtil;
import com.oracle.truffle.js.nodes.JSNodeUtil;
import com.oracle.truffle.js.nodes.access.PropertyNode;
import com.oracle.truffle.js.nodes.access.ReadElementNode;
import com.oracle.truffle.js.nodes.control.ForNode;
import com.oracle.truffle.js.nodes.control.IfNode;
import com.oracle.truffle.js.nodes.control.WhileNode;
import com.oracle.truffle.js.nodes.instrumentation.JSTaggedExecutionNode;
import com.oracle.truffle.js.nodes.instrumentation.JSTags;
public class MaterializationCorrectnessTest extends FineGrainedAccessTest {
@Test
public void testNoDoubleMaterializationReadElementNode() {
Source source = evalAllTags("var a = [1]; a[0];");
assertGlobalArrayLiteralDeclaration("a");
List<ReadElementNode> readElementNodeList = new ArrayList<>();
testNoDoubleMaterializationReadElementNodeCheck(readElementNodeList);
assertFalse(readElementNodeList.isEmpty());
InstrumentedNodesExecutionEventListener listener = new InstrumentedNodesExecutionEventListener(ReadElementNode.class);
instrumenter.attachExecutionEventListener(SourceSectionFilter.ANY, listener);
evalWithCurrentBinding(source);
assertGlobalArrayLiteralDeclaration("a");
List<ReadElementNode> readElementNodeList2 = new ArrayList<>();
testNoDoubleMaterializationReadElementNodeCheck(readElementNodeList2);
assertFalse(readElementNodeList2.isEmpty());
listener.checkEnteredNodes(readElementNodeList);
listener.checkExitedNodes(readElementNodeList);
listener.checkEnteredNodes(readElementNodeList2);
listener.checkExitedNodes(readElementNodeList2);
assertFalse(JSNodeUtil.isTaggedNode(readElementNodeList.get(0).getIndexNode()));
assertFalse(JSNodeUtil.isTaggedNode(readElementNodeList.get(0).getTarget()));
}
private void testNoDoubleMaterializationReadElementNodeCheck(List<ReadElementNode> readElementNodeList) {
enter(JSTags.ReadElementTag.class, (e, elem) -> {
assertTrue(e.instrumentedNode instanceof ReadElementNode);
readElementNodeList.add((ReadElementNode) e.instrumentedNode);
enter(JSTags.ReadPropertyTag.class).input().exit();
elem.input();
enter(JSTags.LiteralTag.class).exit();
elem.input();
}).exit();
}
@Test
public void testNoDoubleMaterializationReadElementNodeNoSourceSectionTargetAndIndex() {
Source source = evalWithTag("var u=[2,4,6]; var p = 1; u[p] -= 42", JSTags.ReadElementTag.class);
List<ReadElementNode> readElementNodeList = new ArrayList<>();
testNoDoubleMaterializationReadElementNodeNoSourceSectionTargetAndIndexCheck(readElementNodeList);
assertFalse(readElementNodeList.isEmpty());
InstrumentedNodesExecutionEventListener listener = new InstrumentedNodesExecutionEventListener(ReadElementNode.class);
instrumenter.attachExecutionEventListener(SourceSectionFilter.newBuilder().tagIs(JSTags.ReadElementTag.class).build(), listener);
evalWithCurrentBinding(source);
List<ReadElementNode> readElementNodeList2 = new ArrayList<>();
testNoDoubleMaterializationReadElementNodeNoSourceSectionTargetAndIndexCheck(readElementNodeList2);
assertFalse(readElementNodeList2.isEmpty());
listener.checkEnteredNodes(readElementNodeList);
listener.checkExitedNodes(readElementNodeList);
listener.checkEnteredNodes(readElementNodeList2);
listener.checkExitedNodes(readElementNodeList2);
assertTrue(JSNodeUtil.isTaggedNode(readElementNodeList.get(0).getIndexNode()));
assertTrue(JSNodeUtil.isTaggedNode(readElementNodeList.get(0).getTarget()));
}
private void testNoDoubleMaterializationReadElementNodeNoSourceSectionTargetAndIndexCheck(List<ReadElementNode> readElementNodeList) {
enter(JSTags.ReadElementTag.class, (e, b) -> {
assertTrue(e.instrumentedNode instanceof ReadElementNode);
readElementNodeList.add((ReadElementNode) e.instrumentedNode);
}).input().input().exit();
}
@Test
public void testNoDoubleMaterializationIfNode() {
Source source = evalAllTags("if (!true) {};");
List<IfNode> ifNodeList = new ArrayList<>();
List<JSTaggedExecutionNode> thenNodeList = new ArrayList<>();
testNoDoubleMaterializationIfNodeCheck(ifNodeList, thenNodeList);
assertFalse(ifNodeList.isEmpty());
assertFalse(thenNodeList.isEmpty());
InstrumentedNodesExecutionEventListener listener = new InstrumentedNodesExecutionEventListener(IfNode.class);
instrumenter.attachExecutionEventListener(SourceSectionFilter.ANY, listener);
evalWithCurrentBinding(source);
List<IfNode> ifNodeList2 = new ArrayList<>();
List<JSTaggedExecutionNode> thenNodeList2 = new ArrayList<>();
testNoDoubleMaterializationIfNodeCheck(ifNodeList2, thenNodeList2);
assertFalse(ifNodeList2.isEmpty());
assertFalse(thenNodeList2.isEmpty());
listener.checkEnteredNodes(ifNodeList);
listener.checkExitedNodes(ifNodeList);
listener.checkEnteredNodes(ifNodeList2);
listener.checkExitedNodes(ifNodeList2);
assertTrue(JSNodeUtil.isTaggedNode(ifNodeList.get(0).getCondition()));
assertTrue(JSNodeUtil.isTaggedNode(ifNodeList.get(0).getThenPart()));
}
private void testNoDoubleMaterializationIfNodeCheck(List<IfNode> ifNodeList, List<JSTaggedExecutionNode> thenNodeList) {
enter(JSTags.ControlFlowRootTag.class, (e1, ifbody) -> {
assertTrue(e1.instrumentedNode instanceof IfNode);
ifNodeList.add((IfNode) e1.instrumentedNode);
enter(JSTags.ControlFlowBranchTag.class, (e2, ifstatement) -> {
assertTrue(e2.instrumentedNode instanceof JSTaggedExecutionNode);
thenNodeList.add((JSTaggedExecutionNode) e2.instrumentedNode);
enter(JSTags.LiteralTag.class).exit();
ifstatement.input();
}).exit();
ifbody.input();
}).exit();
}
@Test
public void testNoDoubleMaterializationWhileNode() {
testNoDoubleMaterializationWhileOrForNode("for (var a=0; a<3; a++) { 42;};", WhileNode.class);
}
@Test
public void testNoDoubleMaterializationForNode() {
testNoDoubleMaterializationWhileOrForNode("for (let i = 0; i < 3; i++) { function dummy(){return i;} };", ForNode.class);
}
private void testNoDoubleMaterializationWhileOrForNode(String src, Class<?> whileOrForNodeClass) {
Source source = evalWithTags(src, new Class[]{
JSTags.ControlFlowRootTag.class,
JSTags.ControlFlowBranchTag.class,
JSTags.ControlFlowBlockTag.class
}, new Class[]{});
List<Node> whileOrForNodeList = new ArrayList<>();
testNoDoubleMaterializationWhileOrForNodeCheck(whileOrForNodeList, whileOrForNodeClass);
assertFalse(whileOrForNodeList.isEmpty());
InstrumentedNodesExecutionEventListener listener = new InstrumentedNodesExecutionEventListener(whileOrForNodeClass);
instrumenter.attachExecutionEventListener(SourceSectionFilter.newBuilder().tagIs(JSTags.ControlFlowRootTag.class,
JSTags.ControlFlowBranchTag.class,
JSTags.ControlFlowBlockTag.class).build(), listener);
evalWithCurrentBinding(source);
List<Node> whileOrForNodeList2 = new ArrayList<>();
testNoDoubleMaterializationWhileOrForNodeCheck(whileOrForNodeList2, whileOrForNodeClass);
assertFalse(whileOrForNodeList2.isEmpty());
listener.checkEnteredNodes(whileOrForNodeList);
listener.checkExitedNodes(whileOrForNodeList);
listener.checkEnteredNodes(whileOrForNodeList2);
listener.checkExitedNodes(whileOrForNodeList);
assertTrue(NodeUtil.findNodeChildren(whileOrForNodeList.get(0)).get(0) instanceof LoopNode);
assertTrue(NodeUtil.findNodeChildren((Node) ((LoopNode) NodeUtil.findNodeChildren(whileOrForNodeList.get(0)).get(0)).getRepeatingNode()).get(1) instanceof InstrumentableNode.WrapperNode);
assertTrue(JSNodeUtil.isTaggedNode(NodeUtil.findNodeChildren((Node) ((LoopNode) NodeUtil.findNodeChildren(whileOrForNodeList.get(0)).get(0)).getRepeatingNode()).get(1)));
}
private void testNoDoubleMaterializationWhileOrForNodeCheck(List<Node> whileNodeList, Class<?> whileOrForNodeClass) {
enter(JSTags.ControlFlowRootTag.class, (e) -> {
assertTrue(whileOrForNodeClass.isAssignableFrom(e.instrumentedNode.getClass()));
whileNodeList.add(e.instrumentedNode);
for (int a = 0; a < 3; a++) {
enter(JSTags.ControlFlowBranchTag.class).exit();
enter(JSTags.ControlFlowBlockTag.class).exit();
}
enter(JSTags.ControlFlowBranchTag.class).exit();
}).exit();
}
@Test
public void testNoDoubleMaterializationPropertyNode() {
Source source = evalAllTags("var a = {x:42}; a.x;");
List<PropertyNode> propertyNodeList = new ArrayList<>();
testNoDoubleMaterializationPropertyNodeCheck(propertyNodeList);
assertFalse(propertyNodeList.isEmpty());
InstrumentedNodesExecutionEventListener listener = new InstrumentedNodesExecutionEventListener(PropertyNode.class);
instrumenter.attachExecutionEventListener(SourceSectionFilter.ANY, listener);
evalWithCurrentBinding(source);
List<PropertyNode> propertyNodeList2 = new ArrayList<>();
testNoDoubleMaterializationPropertyNodeCheck(propertyNodeList2);
assertFalse(propertyNodeList2.isEmpty());
listener.checkEnteredNodes(propertyNodeList);
listener.checkExitedNodes(propertyNodeList);
listener.checkEnteredNodes(propertyNodeList2);
listener.checkExitedNodes(propertyNodeList2);
assertTrue(JSNodeUtil.isTaggedNode(propertyNodeList.get(0).getTarget()));
}
private void testNoDoubleMaterializationPropertyNodeCheck(List<PropertyNode> propertyNodeList) {
enter(JSTags.WritePropertyTag.class, (e, write) -> {
write.input();
enter(JSTags.LiteralTag.class, (e2) -> {
enter(JSTags.LiteralTag.class).exit();
}).input().exit();
}).input().exit();
enter(JSTags.ReadPropertyTag.class, (e) -> {
Assert.assertTrue(e.instrumentedNode instanceof PropertyNode);
propertyNodeList.add((PropertyNode) e.instrumentedNode);
enter(JSTags.ReadPropertyTag.class).input().exit();
}).input().exit();
}
@Test
public void nestedInvokeReadsMultipleInstrumentation() {
Source source = evalWithTags("function setKey(obj, keys) {" +
" obj.a;" +
" keys.slice[0][1][2](0, -1).forEach(function(key) {});" +
"};" +
"var callable = {" +
" slice : [['',['','',function fakeslice() { return [1,2]; }]]]" +
"};" +
"setKey({}, callable);" +
"for (var i = 0; i < 2; i++) {" +
" setKey({" +
" a: 1" +
" }, callable);" +
"}", new Class[]{JSTags.ReadElementTag.class, JSTags.FunctionCallTag.class});
nestedInvokeReadsMultipleInstrumentationCheck();
instrumenter.attachExecutionEventListener(SourceSectionFilter.ANY, new ExecutionEventListener() {
@Override
public void onEnter(EventContext c, VirtualFrame frame) {
}
@Override
public void onReturnValue(EventContext c, VirtualFrame frame, Object result) {
}
@Override
public void onReturnExceptional(EventContext c, VirtualFrame frame, Throwable exception) {
}
});
evalWithCurrentBinding(source);
nestedInvokeReadsMultipleInstrumentationCheck();
}
private void nestedInvokeReadsMultipleInstrumentationCheck() {
for (int i = 0; i < 3; i++) {
enter(JSTags.FunctionCallTag.class, (e, elem) -> {
elem.input().input().input().input();
enter(JSTags.FunctionCallTag.class, (e1, elem1) -> {
enter(JSTags.FunctionCallTag.class, (e2, elem2) -> {
enter(JSTags.ReadElementTag.class, (e3, elem3) -> {
enter(JSTags.ReadElementTag.class, (e4, elem4) -> {
}).input().input().exit();
elem3.input();
elem3.input();
}).exit();
elem2.input();
enter(JSTags.ReadElementTag.class, (e3, elem3) -> {
elem3.input();
elem3.input();
}).exit();
elem2.input().input().input();
}).exit();
elem1.input().input().input();
}).exit();
}).exit();
}
}
@Test
public void nestedInvokeReadsMultipleDifferentInstrumentation() {
Source source = eval("function setKey(obj, keys) {" +
" obj.a;" +
" keys.slice[0][1][2](0, -1).forEach(function(key) {});" +
"};" +
"var callable = {" +
" slice : [['',['','',function fakeslice() { return [1,2]; }]]]" +
"};" +
"setKey({}, callable);" +
"for (var i = 0; i < 2; i++) {" +
" setKey({" +
" a: 1" +
" }, callable);" +
"}");
evalWithTags(source, new Class[]{JSTags.FunctionCallTag.class}, new Class[]{});
for (int i = 0; i < 3; i++) {
enter(JSTags.FunctionCallTag.class, (e, elem) -> {
enter(JSTags.FunctionCallTag.class, (e1, elem1) -> {
enter(JSTags.FunctionCallTag.class, (e2, elem2) -> {
}).exit();
}).exit();
}).exit();
}
evalWithNewTags(source, new Class[]{JSTags.FunctionCallTag.class, JSTags.ReadElementTag.class}, new Class[]{});
nestedInvokeReadsNoInputInstrumentationCheck();
}
@Test
public void nestedInvokeReadsNoInputInstrumentation() {
evalWithTags("function setKey(obj, keys) {" +
" obj.a;" +
" keys.slice[0][1][2](0, -1).forEach(function(key) {});" +
"};" +
"var callable = {" +
" slice : [['',['','',function fakeslice() { return [1,2]; }]]]" +
"};" +
"setKey({}, callable);" +
"for (var i = 0; i < 2; i++) {" +
" setKey({" +
" a: 1" +
" }, callable);" +
"}", new Class[]{JSTags.FunctionCallTag.class, JSTags.ReadElementTag.class}, new Class[]{});
nestedInvokeReadsNoInputInstrumentationCheck();
}
private void nestedInvokeReadsNoInputInstrumentationCheck() {
for (int i = 0; i < 3; i++) {
enter(JSTags.FunctionCallTag.class, (e, elem) -> {
enter(JSTags.FunctionCallTag.class, (e1, elem1) -> {
enter(JSTags.FunctionCallTag.class, (e2, elem2) -> {
enter(JSTags.ReadElementTag.class, (e3, elem3) -> {
enter(JSTags.ReadElementTag.class, (e4, elem4) -> {
}).exit();
}).exit();
enter(JSTags.ReadElementTag.class, (e3, elem3) -> {
}).exit();
}).exit();
}).exit();
}).exit();
}
}
}