package com.oracle.truffle.js.test.instrumentation;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.FunctionCallTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.LiteralTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ObjectAllocationTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ReadElementTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ReadPropertyTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.WritePropertyTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.WriteVariableTag;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.objects.Undefined;
public class CallAccessTest extends FineGrainedAccessTest {
@Test
public void callOneArg() {
evalAllTags("function foo(a) {}; foo(42);");
assertGlobalFunctionExpressionDeclaration("foo");
enter(FunctionCallTag.class, (e, call) -> {
enter(LiteralTag.class).exit(assertReturnValue(Undefined.instance));
call.input(assertUndefinedInput);
enter(ReadPropertyTag.class).input(assertGlobalObjectInput).exit();
call.input(assertJSFunctionInput);
enter(LiteralTag.class).exit(assertReturnValue(42));
call.input(42);
enterDeclareTag("a");
enter(WriteVariableTag.class, (e1, call1) -> {
call1.input(42);
}).exit();
}).exit();
}
@Test
public void callTwoArgs() {
evalAllTags("function foo(a,b) {}; foo(42,24);");
assertGlobalFunctionExpressionDeclaration("foo");
enter(FunctionCallTag.class, (e, call) -> {
enter(LiteralTag.class).exit(assertReturnValue(Undefined.instance));
call.input(assertUndefinedInput);
enter(ReadPropertyTag.class).input(assertGlobalObjectInput).exit();
call.input(assertJSFunctionInput);
enter(LiteralTag.class).exit(assertReturnValue(42));
call.input(42);
enter(LiteralTag.class).exit(assertReturnValue(24));
call.input(24);
enterDeclareTag("a");
enterDeclareTag("b");
enter(WriteVariableTag.class, (e1, call1) -> {
call1.input(42);
}).exit();
enter(WriteVariableTag.class, (e1, call1) -> {
call1.input(24);
}).exit();
}).exit();
}
@Test
public void methodCall() {
evalAllTags("var foo = {x:function foo(a,b) {}}; foo.x(42,24);");
enter(WritePropertyTag.class, (e, write) -> {
assertAttribute(e, KEY, "foo");
write.input(assertJSObjectInput);
enter(LiteralTag.class, (e1, literal) -> {
assertAttribute(e1, LITERAL_TYPE, LiteralTag.Type.ObjectLiteral.name());
enter(LiteralTag.class, (e2) -> {
assertAttribute(e2, LITERAL_TYPE, LiteralTag.Type.FunctionLiteral.name());
}).exit();
literal.input(assertJSFunctionInput);
}).exit();
write.input(assertJSObjectInput);
}).exit();
enter(FunctionCallTag.class, (e, call) -> {
enter(ReadPropertyTag.class, (e1, prop) -> {
assertAttribute(e1, KEY, "foo");
prop.input(assertGlobalObjectInput);
}).exit();
call.input(assertJSObjectInput);
enter(ReadPropertyTag.class, assertPropertyReadName("x")).input(assertJSObjectInput).exit();
call.input(assertJSFunctionInput);
enter(LiteralTag.class).exit(assertReturnValue(42));
call.input(42);
enter(LiteralTag.class).exit(assertReturnValue(24));
call.input(24);
enterDeclareTag("a");
enterDeclareTag("b");
enter(WriteVariableTag.class, (e1, call1) -> {
call1.input(42);
}).exit();
enter(WriteVariableTag.class, (e1, call1) -> {
call1.input(24);
}).exit();
}).exit();
}
@Test
public void methodCallOneArg() {
evalAllTags("var foo = {x:function foo(a,b) {}}; foo.x(42);");
enter(WritePropertyTag.class, (e, write) -> {
assertAttribute(e, KEY, "foo");
write.input(assertJSObjectInput);
enter(LiteralTag.class, (e1, literal) -> {
assertAttribute(e1, LITERAL_TYPE, LiteralTag.Type.ObjectLiteral.name());
enter(LiteralTag.class, (e2) -> {
assertAttribute(e2, LITERAL_TYPE, LiteralTag.Type.FunctionLiteral.name());
}).exit();
literal.input(assertJSFunctionInput);
}).exit();
write.input(assertJSObjectInput);
}).exit();
enter(FunctionCallTag.class, (e, call) -> {
enter(ReadPropertyTag.class, (e1, prop) -> {
assertAttribute(e1, KEY, "foo");
prop.input(assertGlobalObjectInput);
}).exit();
call.input(assertJSObjectInput);
enter(ReadPropertyTag.class, assertPropertyReadName("x")).input().exit();
call.input(assertJSFunctionInput);
enter(LiteralTag.class).exit(assertReturnValue(42));
call.input(42);
enterDeclareTag("a");
enterDeclareTag("b");
enter(WriteVariableTag.class, (e1, call1) -> {
call1.input(42);
}).exit();
enter(WriteVariableTag.class, (e1, call1) -> {
call1.input(Undefined.instance);
}).exit();
}).exit();
}
@Test
public void methodCallElementArg() {
evalAllTags("var a = {x:[function(){}]}; a.x[0](42);");
enter(WritePropertyTag.class, (e, write) -> {
assertAttribute(e, KEY, "a");
write.input(assertGlobalObjectInput);
enter(LiteralTag.class, (e1, oblit) -> {
assertAttribute(e1, LITERAL_TYPE, LiteralTag.Type.ObjectLiteral.name());
enter(LiteralTag.class, (e2, arrlit) -> {
assertAttribute(e2, LITERAL_TYPE, LiteralTag.Type.ArrayLiteral.name());
enter(LiteralTag.class, (e3) -> {
assertAttribute(e3, LITERAL_TYPE, LiteralTag.Type.FunctionLiteral.name());
}).exit();
arrlit.input(assertJSFunctionInput);
}).exit();
oblit.input(assertJSArrayInput);
}).exit();
write.input(assertJSObjectInput);
}).exit();
enter(FunctionCallTag.class, (e, call) -> {
enter(ReadPropertyTag.class, (e1, prop) -> {
assertAttribute(e1, KEY, "x");
enter(ReadPropertyTag.class, (e2, p2) -> {
assertAttribute(e2, KEY, "a");
p2.input(assertGlobalObjectInput);
}).exit();
prop.input(assertJSObjectInput);
}).exit();
call.input(assertJSArrayInput);
enter(ReadElementTag.class, (e1, el) -> {
el.input(assertJSArrayInput);
enter(LiteralTag.class).exit(assertReturnValue(0));
el.input(0);
}).exit();
call.input(assertJSFunctionInput);
enter(LiteralTag.class).exit(assertReturnValue(42));
call.input(42);
}).exit(assertReturnValue(Undefined.instance));
}
@Test
public void newTest() {
evalWithTags("function A() {}; var a = {x:function(){return 1;}}; new A(a.x(), a.x());", new Class<?>[]{ObjectAllocationTag.class, FunctionCallTag.class});
enter(ObjectAllocationTag.class, (e, call) -> {
call.input(assertJSFunctionInput);
enter(FunctionCallTag.class).input().input().exit();
call.input(1);
enter(FunctionCallTag.class).input().input().exit();
call.input(1);
}).exit((r) -> {
Object[] vals = (Object[]) r.val;
assertTrue(vals[2].equals(1));
assertTrue(vals[3].equals(1));
assertTrue(JSFunction.isJSFunction(vals[1]));
});
}
@Test
public void changeFunc() {
String src = "function foo(a){return a;}" +
"function bar(b){return b;}" +
"function run() {this.f();}" +
"function T() {this.f = foo;this.r = run;}" +
"for(var i = 0; i < 2; i++) {" +
" var t = new T();" +
" t.r();" +
" t.f = bar;" +
" t.r();" +
"}";
evalWithTags(src, new Class<?>[]{ObjectAllocationTag.class, FunctionCallTag.class});
enter(ObjectAllocationTag.class, (e, call) -> {
call.input(assertJSFunctionInput);
}).exit();
enter(FunctionCallTag.class, (e, call) -> {
call.input(assertJSObjectInput);
call.input(assertJSFunctionInput);
enter(FunctionCallTag.class, (e2, call2) -> {
call2.input(assertJSObjectInput);
call2.input(assertJSFunctionInput);
}).exit();
}).exit();
enter(FunctionCallTag.class, (e, call) -> {
call.input(assertJSObjectInput);
call.input(assertJSFunctionInput);
enter(FunctionCallTag.class, (e2, call2) -> {
call2.input(assertJSObjectInput);
call2.input(assertJSFunctionInput);
}).exit();
}).exit();
enter(ObjectAllocationTag.class, (e, call) -> {
call.input(assertJSFunctionInput);
}).exit();
enter(FunctionCallTag.class, (e, call) -> {
call.input(assertJSObjectInput);
call.input(assertJSFunctionInput);
enter(FunctionCallTag.class, (e2, call2) -> {
call2.input(assertJSObjectInput);
call2.input(assertJSFunctionInput);
}).exit();
}).exit();
enter(FunctionCallTag.class, (e, call) -> {
call.input(assertJSObjectInput);
call.input(assertJSFunctionInput);
enter(FunctionCallTag.class, (e2, call2) -> {
call2.input(assertJSObjectInput);
call2.input(assertJSFunctionInput);
}).exit();
}).exit();
}
@Test
public void castCrash() {
String src = "function foo(){var fArr = [function (){}];for(i in fArr) {fArr[i]();}} foo();";
evalWithTag(src, FunctionCallTag.class);
enter(FunctionCallTag.class, (e, call) -> {
call.input(assertJSObjectInput);
call.input(assertJSFunctionInput);
enter(FunctionCallTag.class, (e2, call2) -> {
call2.input(assertJSArrayInput);
call2.input(assertJSFunctionInput);
}).exit();
}).exit();
}
@Test
public void callForeignTest() {
String src = "var r = Polyglot.import('run'); r.run();";
declareInteropSymbol("run", new ForeignTestObject());
evalWithTag(src, FunctionCallTag.class);
enter(FunctionCallTag.class, (e, call) -> {
call.input(assertJSObjectInput);
call.input(assertJSFunctionInput);
call.input("run");
}).exit();
enter(FunctionCallTag.class, (e, call) -> {
call.input(assertTruffleObject);
call.input(assertTruffleObject);
}).exit();
}
@Test
public void invokeGlobal() {
String src = "arr=new Array();\n" +
"for(var a = 0; a < 100; a++){\n" +
" arr.push(\"\");\n" +
"}";
evalWithTags(src, new Class<?>[]{ObjectAllocationTag.class, FunctionCallTag.class});
enter(ObjectAllocationTag.class, (e, call) -> {
call.input(assertJSFunctionInput);
}).exit();
for (int i = 0; i < 100; i++) {
enter(FunctionCallTag.class, (e, call) -> {
call.input(assertJSArrayInput);
call.input(assertJSFunctionInput);
call.input("");
}).exit();
}
}
@Test
public void restArgs() {
evalWithTag("function foo(...args) {" +
" return bar(...args);" +
"};" +
"function bar() {" +
" return arguments[0];" +
"};" +
"foo(42);", FunctionCallTag.class);
enter(FunctionCallTag.class, (e, fooCall) -> {
fooCall.input(assertJSObjectInput);
fooCall.input(assertJSFunctionInput);
fooCall.input(42);
enter(FunctionCallTag.class, (e2, barCall) -> {
barCall.input(assertJSObjectInput);
barCall.input(assertJSFunctionInput);
barCall.input(assertJSArrayInput);
}).exit();
}).exit(assertReturnValue(42));
}
@Test
public void restArgsMulti() {
evalWithTag("function foo(x, y, ...args) {" +
" return bar(x, y, ...args);" +
"};" +
"function bar() {" +
" return arguments[4];" +
"};" +
"foo('a', 'b', 40, 41, 42);", FunctionCallTag.class);
enter(FunctionCallTag.class, (e, fooCall) -> {
fooCall.input(assertJSObjectInput);
fooCall.input(assertJSFunctionInput);
fooCall.input("a");
fooCall.input("b");
fooCall.input(40);
fooCall.input(41);
fooCall.input(42);
enter(FunctionCallTag.class, (e2, barCall) -> {
barCall.input(assertJSObjectInput);
barCall.input(assertJSFunctionInput);
barCall.input("a");
barCall.input("b");
barCall.input(assertJSArrayInput);
}).exit(assertReturnValue(42));
}).exit(assertReturnValue(42));
}
@Test
public void supeCallTest() {
evalWithTags("class Base {" +
" constructor() {" +
" this.someObj = {};" +
" };" +
" def() {" +
" return this.someObj;" +
" };" +
"};" +
"class Bar extends Base {" +
" use() {" +
" return super.def();" +
" };" +
"};" +
"var bar = new Bar();" +
"bar.use();", new Class<?>[]{ObjectAllocationTag.class, FunctionCallTag.class});
enter(ObjectAllocationTag.class, (e, newCall) -> {
newCall.input(assertJSFunctionInputWithName("Bar"));
}).exit(assertJSObjectReturn);
enter(FunctionCallTag.class, (e2, useCall) -> {
useCall.input(assertJSObjectInput);
useCall.input(assertJSFunctionInputWithName("use"));
enter(FunctionCallTag.class, (e3, defCall) -> {
defCall.input(assertJSObjectInput);
defCall.input(assertJSFunctionInputWithName("def"));
}).exit(assertJSObjectReturn);
}).exit(assertJSObjectReturn);
}
@Test
public void splitMaterializedCallTest() {
evalWithTags("function setKey(obj, keys) {" +
" obj.a;" +
" keys.slice(0, -1).forEach(function(key) {});" +
"};" +
"setKey({}, ['a']);" +
"for(var i =0; i<2; i++) {" +
" setKey({a:1}, ['a']);" +
"};", new Class<?>[]{ObjectAllocationTag.class, FunctionCallTag.class});
for (int i = 0; i < 3; i++) {
enter(FunctionCallTag.class, (e, call) -> {
call.input(assertUndefinedInput);
call.input(assertJSFunctionInputWithName("setKey"));
call.input(assertJSObjectInput);
call.input(assertJSArrayInput);
enter(FunctionCallTag.class, (e1, call1) -> {
enter(FunctionCallTag.class, (e2, call2) -> {
call2.input(assertJSArrayInput);
call2.input(assertJSFunctionInputWithName("slice"));
call2.input(0);
call2.input(-1);
}).exit();
call1.input(assertJSArrayInput);
call1.input(assertJSFunctionInputWithName("forEach"));
call1.input(assertJSFunctionInput);
}).exit();
}).exit();
}
}
@Test
public void splitMaterializedElementCallTest() {
evalWithTag("function setKey(obj, keys) {" +
" obj.a;" +
" keys.slice[0][1][2](0, -1).forEach(function(key) {});" +
"};" +
"const callable = {" +
" slice : [['',['','',function fakeslice() { return [1,2]; }]]]" +
"};" +
"setKey({}, callable);" +
"for (var i = 0; i < 2; i++) {" +
" setKey({" +
" a: 1" +
" }, callable);" +
"};", FunctionCallTag.class);
for (int i = 0; i < 3; i++) {
enter(FunctionCallTag.class, (e, call) -> {
call.input(assertUndefinedInput);
call.input(assertJSFunctionInputWithName("setKey"));
call.input(assertJSObjectInput);
call.input(assertJSObjectInput);
enter(FunctionCallTag.class, (e1, call1) -> {
enter(FunctionCallTag.class, (e2, call2) -> {
call2.input(assertJSArrayInput);
call2.input(assertJSFunctionInputWithName("fakeslice"));
call2.input(0);
call2.input(-1);
}).exit();
call1.input(assertJSArrayInput);
call1.input(assertJSFunctionInputWithName("forEach"));
call1.input(assertJSFunctionInput);
}).exit();
}).exit();
}
}
@Test
public void doWith() {
evalWithTag("function bar() {" +
" var obj = {" +
" foo: function(){}" +
" };" +
" with(obj)" +
" return foo('str', 42);" +
"}" +
"bar();", FunctionCallTag.class);
enter(FunctionCallTag.class, (e, barCall) -> {
barCall.input(assertUndefinedInput);
barCall.input(assertJSFunctionInputWithName("bar"));
enter(FunctionCallTag.class, (e2, fooCall) -> {
fooCall.input(assertJSObjectInput);
fooCall.input(assertJSFunctionInputWithName("foo"));
fooCall.input("str");
fooCall.input(42);
}).exit();
}).exit();
}
@Test
public void github367Private() {
evalWithTags("class C { #x = function() {}; m() { this.#x(42); } }; new C().m()", new Class[]{ObjectAllocationTag.class, FunctionCallTag.class});
enter(FunctionCallTag.class, (e, mCall) -> {
enter(ObjectAllocationTag.class, (n, newClassCall) -> {
newClassCall.input(assertJSFunctionInputWithName("C"));
}).exit(assertJSObjectReturn);
mCall.input(assertJSObjectInput);
mCall.input(assertJSFunctionInputWithName("m"));
enter(FunctionCallTag.class, (e2, fieldCall) -> {
fieldCall.input(assertJSObjectInput);
fieldCall.input(assertJSFunctionInputWithName("#x"));
fieldCall.input(42);
}).exit();
}).exit();
}
@Test
public void github367OptionalCall() {
evalWithTags("var f = function() { return 42; }; f?.();", new Class[]{FunctionCallTag.class});
enter(FunctionCallTag.class, (e, fCall) -> {
fCall.input(assertGlobalObjectInput);
fCall.input(assertJSFunctionInputWithName("f"));
}).exit(assertReturnValue(42));
}
@Test
public void github367OptionalProperty() {
evalWithTags("o = {}; o.f = function foo() { return 42; }; (o?.f)();", new Class[]{FunctionCallTag.class});
enter(FunctionCallTag.class, (e, fCall) -> {
fCall.input(assertJSObjectInput);
fCall.input(assertJSFunctionInputWithName("foo"));
}).exit(assertReturnValue(42));
}
@Test
public void github367OptionalElement() {
evalWithTags("o = {}; o.f = function foo() { return 42; }; (o?.['f'])();", new Class[]{FunctionCallTag.class});
enter(FunctionCallTag.class, (e, fCall) -> {
fCall.input(assertJSObjectInput);
fCall.input(assertJSFunctionInputWithName("foo"));
}).exit(assertReturnValue(42));
}
}