/* * Copyright (c) 2017, 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.metadata.test; import java.util.Iterator; import java.util.Map; 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; import com.oracle.truffle.api.CallTarget; import com.oracle.truffle.api.Truffle; import com.oracle.truffle.api.TruffleLanguage; import com.oracle.truffle.api.frame.Frame; 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.Instrumentable; import com.oracle.truffle.api.instrumentation.ProvidedTags; import com.oracle.truffle.api.metadata.Scope; import com.oracle.truffle.api.instrumentation.SourceSectionFilter; import com.oracle.truffle.api.instrumentation.StandardTags; import com.oracle.truffle.api.instrumentation.TruffleInstrument; import com.oracle.truffle.api.instrumentation.test.AbstractInstrumentationTest; import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.java.JavaInterop; import com.oracle.truffle.api.metadata.ScopeProvider; import com.oracle.truffle.api.metadata.ScopeProvider.AbstractScope; 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.source.SourceSection; /** * Test of {@link Scope}. */ public class ScopeTest extends AbstractInstrumentationTest { @Test public void testDefaultScope() throws Throwable { engine.getRuntime().getInstruments().get("testScopeInstrument").setEnabled(true); TestScopeInstrument.INSTANCE.setTester(new DefaultScopeTester()); run("ROOT(DEFINE(testFunction,\nROOT(\nVARIABLE(a, 10),\nVARIABLE(b, 20),\nSTATEMENT)),\nCALL(testFunction))"); TestScopeInstrument.INSTANCE.checkForFailure(); } @TruffleInstrument.Registration(id = "testScopeInstrument") public static class TestScopeInstrument extends TruffleInstrument { static TestScopeInstrument INSTANCE; private Tester tester; private boolean scopeTested; private Throwable failure; @Override protected void onCreate(TruffleInstrument.Env env) { INSTANCE = this; env.getInstrumenter().attachListener(SourceSectionFilter.ANY, new ExecutionEventListener() { @Override public void onEnter(EventContext context, VirtualFrame frame) { scopeTested = true; try { tester.doTestScope(env, context.getInstrumentedNode(), frame); } catch (Throwable t) { failure = t; } } @Override public void onReturnValue(EventContext context, VirtualFrame frame, Object result) { } @Override public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) { } }); } void setTester(Tester tester) { scopeTested = false; this.tester = tester; } void checkForFailure() throws Throwable { tester = null; assertTrue("Scope instrument not triggered", scopeTested); if (failure != null) { throw failure; } } interface Tester { void doTestScope(TruffleInstrument.Env env, Node node, VirtualFrame frame); } } private static class DefaultScopeTester implements TestScopeInstrument.Tester { @SuppressWarnings("rawtypes") public void doTestScope(TruffleInstrument.Env env, Node node, VirtualFrame frame) { Iterable<Scope> scopes = Scope.findScopes(env, node, null); assertNotNull(scopes); Iterator<Scope> iterator = scopes.iterator(); assertTrue(iterator.hasNext()); Scope scope = iterator.next(); assertFalse(iterator.hasNext()); int line = node.getSourceSection().getStartLine(); if (line == 1 || line == 6) { assertEquals("Line = " + line + ", function name: ", "", scope.getName()); } else { assertEquals("Line = " + line + ", function name: ", "testFunction", scope.getName()); // Lexical access: TruffleObject vars = (TruffleObject) scope.getVariables(null); Map varsMap = JavaInterop.asJavaObject(Map.class, vars); final int numVars = Math.max(line - 3, 0); assertEquals("Line = " + line + ", num vars:", numVars, varsMap.size()); if (numVars >= 1) { assertTrue("Var a: ", varsMap.containsKey("a")); try { varsMap.get("a"); fail(); } catch (Exception ex) { // variable value can not be read in the static access } } if (numVars >= 2) { assertTrue("Var b: ", varsMap.containsKey("b")); try { varsMap.get("b"); fail(); } catch (Exception ex) { // variable value can not be read in the static access } } // Dynamic access: vars = (TruffleObject) scope.getVariables(frame); varsMap = JavaInterop.asJavaObject(Map.class, vars); assertEquals("Line = " + line + ", num vars:", numVars, varsMap.size()); if (numVars >= 1) { assertTrue("Var a: ", varsMap.containsKey("a")); assertEquals("Var a: ", 10, varsMap.get("a")); } if (numVars >= 2) { assertTrue("Var b: ", varsMap.containsKey("b")); assertEquals("Var b: ", 20, varsMap.get("b")); } } } } @Test public void testSPIScopeCalls() throws Throwable { Source source = Source.newBuilder("test").name("unknown").mimeType("x-testCustomScope").build(); engine.getRuntime().getInstruments().get("testScopeInstrument").setEnabled(true); TestScopeInstrument.INSTANCE.setTester(new CustomScopeTester()); engine.eval(source); TestScopeInstrument.INSTANCE.checkForFailure(); } @TruffleLanguage.Registration(name = "", version = "", mimeType = "x-testCustomScope") @ProvidedTags({StandardTags.StatementTag.class}) public static class CustomScopeLanguage extends TruffleLanguage<Object> implements ScopeProvider<Object> { @Override protected Object createContext(Env env) { return new Object(); } @Override protected Object findExportedSymbol(Object context, String globalName, boolean onlyExplicit) { return null; } @Override protected Object getLanguageGlobal(Object context) { return null; } @Override protected boolean isObjectOfLanguage(Object object) { return true; } @Override protected CallTarget parse(ParsingRequest request) throws Exception { return Truffle.getRuntime().createCallTarget(new CustomRoot(this)); } @Override public AbstractScope findScope(Object context, Node node, Frame frame) { return new CustomScope(node, frame); } public static class CustomRoot extends RootNode { @Child private CustomScopeNode scopeNode = new CustomScopeNode(); public CustomRoot(TruffleLanguage<?> language) { super(language); } @Override public Object execute(VirtualFrame frame) { return scopeNode.execute(frame); } @Override protected boolean isInstrumentable() { return true; } } @Instrumentable(factory = CustomScopeNodeWrapper.class) public static class CustomScopeNode extends Node { public CustomScopeNode() { } @SuppressWarnings("unused") public Object execute(VirtualFrame frame) { return 1; } @Override public SourceSection getSourceSection() { return Source.newBuilder("test").name("unknown").mimeType("x-testCustomScope").build().createSection(1); } @Override protected boolean isTaggedWith(Class<?> tag) { return StandardTags.StatementTag.class.equals(tag); } } } private static class CustomScope extends AbstractScope { // Checkstyle: stop static CustomScope LAST_INSTANCE; static int NUM_INSTANCES = 0; // Checkstyle: resume private final Node node; private final Frame frame; private int numGetNames = 0; private int numGetNodes = 0; private int numGetVariables = 0; private int numGetArguments = 0; private int numFindParents = 0; CustomScope(Node node, Frame frame) { this.node = node; this.frame = frame; LAST_INSTANCE = this; NUM_INSTANCES++; } @Override protected String getName() { numGetNames++; return "CustomScope.getName"; } @Override protected Node getNode() { numGetNodes++; return node; } @Override protected Object getVariables(Frame f) { numGetVariables++; if (f == null) { return "V1"; } else { return "V1V2V3"; } } @Override protected Object getArguments(Frame f) { numGetArguments++; if (f == null) { return "A1"; } else { return "A1A2A3"; } } @Override protected AbstractScope findParent() { numFindParents++; Node parent = node.getParent(); if (parent != null) { return new CustomScope(parent, frame); } else { return null; } } } private static class CustomScopeTester implements TestScopeInstrument.Tester { @Override public void doTestScope(TruffleInstrument.Env env, Node node, VirtualFrame frame) { assertNull(CustomScope.LAST_INSTANCE); assertEquals(0, CustomScope.NUM_INSTANCES); Iterable<Scope> findScopes = Scope.findScopes(env, node, null); assertNotNull(CustomScope.LAST_INSTANCE); assertEquals(1, CustomScope.NUM_INSTANCES); Iterator<Scope> iterator = findScopes.iterator(); assertTrue(iterator.hasNext()); Scope scope = iterator.next(); assertEquals(1, CustomScope.NUM_INSTANCES); testScopeContent(scope, node, frame); assertEquals(1, CustomScope.NUM_INSTANCES); assertTrue(iterator.hasNext()); assertEquals(2, CustomScope.NUM_INSTANCES); scope = iterator.next(); assertEquals(2, CustomScope.NUM_INSTANCES); testScopeContent(scope, node.getParent(), frame); assertEquals(2, CustomScope.NUM_INSTANCES); assertTrue(iterator.hasNext()); assertEquals(3, CustomScope.NUM_INSTANCES); scope = iterator.next(); assertEquals(3, CustomScope.NUM_INSTANCES); assertFalse(iterator.hasNext()); try { iterator.next(); fail(); } catch (Exception ex) { // next should fail } assertEquals(3, CustomScope.NUM_INSTANCES); } private static void testScopeContent(Scope scope, Node node, Frame frame) { assertEquals(0, CustomScope.LAST_INSTANCE.numGetNames); assertEquals("CustomScope.getName", scope.getName()); assertEquals(1, CustomScope.LAST_INSTANCE.numGetNames); assertEquals(0, CustomScope.LAST_INSTANCE.numGetNodes); assertEquals(node, scope.getNode()); assertEquals(1, CustomScope.LAST_INSTANCE.numGetNodes); assertEquals(0, CustomScope.LAST_INSTANCE.numGetVariables); assertEquals("V1", scope.getVariables(null)); assertEquals(1, CustomScope.LAST_INSTANCE.numGetVariables); assertEquals("V1V2V3", scope.getVariables(frame)); assertEquals(2, CustomScope.LAST_INSTANCE.numGetVariables); assertEquals(0, CustomScope.LAST_INSTANCE.numGetArguments); assertEquals("A1", scope.getArguments(null)); assertEquals(1, CustomScope.LAST_INSTANCE.numGetArguments); assertEquals("A1A2A3", scope.getArguments(frame)); assertEquals(2, CustomScope.LAST_INSTANCE.numGetArguments); assertEquals(0, CustomScope.LAST_INSTANCE.numFindParents); } } }