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.BinaryOperationTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.DeclareTag;
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.LiteralTag.Type;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.ReadPropertyTag;
import com.oracle.truffle.js.nodes.instrumentation.JSTags.WritePropertyTag;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
public class PropertyAccessTest extends FineGrainedAccessTest {
@Test
public void read() {
evalAllTags("var a = {x:42}; a.x;");
enter(WritePropertyTag.class, (e, write) -> {
assertAttribute(e, KEY, "a");
write.input(assertGlobalObjectInput);
enter(LiteralTag.class, (e2) -> {
assertAttribute(e2, LITERAL_TYPE, Type.ObjectLiteral.name());
enter(LiteralTag.class).exit();
}).input(42).exit();
}).input((e) -> {
assertTrue(JSDynamicObject.isJSDynamicObject(e.val));
}).exit();
enter(ReadPropertyTag.class, (e) -> {
assertAttribute(e, KEY, "x");
enter(ReadPropertyTag.class).input(assertGlobalObjectInput).exit();
}).input((e) -> {
assertTrue(JSDynamicObject.isJSDynamicObject(e.val));
}).exit();
}
@Test
public void nestedRead() {
evalAllTags("var a = {x:{y:42}}; a.x.y;");
enter(WritePropertyTag.class, (e, write) -> {
assertAttribute(e, KEY, "a");
write.input(assertGlobalObjectInput);
enter(LiteralTag.class, (e2) -> {
enter(LiteralTag.class, (e3) -> {
assertAttribute(e3, LITERAL_TYPE, Type.ObjectLiteral.name());
enter(LiteralTag.class).exit();
}).input(42).exit();
}).input().exit();
write.input(assertJSObjectInput);
}).exit();
enter(ReadPropertyTag.class, (e, prop) -> {
assertAttribute(e, KEY, "y");
enter(ReadPropertyTag.class, (e1, read) -> {
assertAttribute(e1, KEY, "x");
enter(ReadPropertyTag.class).input(assertGlobalObjectInput).exit();
read.input(assertJSObjectInput);
}).exit();
prop.input(assertJSObjectInput);
}).exit();
}
@Test
public void write() {
evalAllTags("var a = {}; a.x = 42;");
enter(WritePropertyTag.class, (e, write) -> {
assertAttribute(e, KEY, "a");
write.input(assertGlobalObjectInput);
enter(LiteralTag.class).exit();
write.input(assertJSObjectInput);
}).exit();
enter(WritePropertyTag.class, (e, write) -> {
assertAttribute(e, KEY, "x");
enter(ReadPropertyTag.class, (e1, p) -> {
assertAttribute(e1, KEY, "a");
p.input(assertGlobalObjectInput);
}).exit();
write.input(assertJSObjectInput);
enter(LiteralTag.class).exit();
write.input(42);
}).exit();
}
@Test
public void read2() {
String src = "var a = {log:function(){}}; a.log(42);";
evalWithTag(src, ReadPropertyTag.class);
enter(ReadPropertyTag.class, (e1, pr1) -> {
assertAttribute(e1, KEY, "a");
pr1.input(assertGlobalObjectInput);
}).exit();
enter(ReadPropertyTag.class, (e1, pr1) -> {
assertAttribute(e1, KEY, "log");
pr1.input(assertJSObjectInput);
}).exit(assertJSFunctionReturn);
}
@Test
public void readMulti() {
String src = "function Bar() {};" +
"var bar = new Bar();" +
"bar.a = {x:function(){}};" +
"for(var i = 0; i < 10; i++) {" +
" bar.a.x(bar);" +
"}";
evalWithTag(src, ReadPropertyTag.class);
assertPropertyRead("Bar");
assertPropertyRead("bar");
assertPropertyRead("i");
assertNestedPropertyRead("a", "bar");
assertPropertyRead("x");
for (int i = 0; i < 9; i++) {
assertPropertyRead("bar");
assertPropertyRead("i");
assertPropertyRead("i");
assertNestedPropertyRead("a", "bar");
assertPropertyRead("x");
}
assertPropertyRead("bar");
assertPropertyRead("i");
assertPropertyRead("i");
}
@Test
public void readMissingSourceSection() {
String src = "function bar(){};" +
"function foo(){" +
" this.v = new bar();" +
"};" +
"foo.prototype.x = function(){" +
" this.y()[0]=1;" +
"};" +
"foo.prototype.y = function(){" +
" return this.v;" +
"};" +
"var cnt = 0;" +
"var a = new foo();" +
"while(cnt < 10) {" +
" a.x();" +
" cnt++;" +
"}";
evalWithTag(src, ReadPropertyTag.class);
assertNestedPropertyRead("prototype", "foo");
assertNestedPropertyRead("prototype", "foo");
assertPropertyRead("foo");
assertPropertyRead("bar");
for (int cnt = 0; cnt < 10; cnt++) {
assertPropertyRead("cnt");
assertPropertyRead("a");
assertPropertyRead("x");
assertPropertyRead("y");
assertPropertyRead("v");
assertPropertyRead("cnt");
}
assertPropertyRead("cnt");
}
@Test
public void readPrototypeInCall() {
String src = "var addProperty = function(color, func) {" +
" String.prototype.__defineGetter__(color, func);" +
"};" +
"var colors = [1,2,3,4,5,6,7,8,9,10];" +
"addProperty(colors[0], function(){});" +
"colors.forEach(function(c) {" +
" addProperty(c, function(){});" +
" }" +
");";
evalWithTag(src, ReadPropertyTag.class);
assertPropertyRead("addProperty");
assertPropertyRead("colors");
assertNestedPropertyRead("prototype", "String");
assertPropertyRead("__defineGetter__");
assertPropertyRead("colors");
assertPropertyRead("forEach");
for (int i = 0; i < 10; i++) {
assertPropertyRead("addProperty");
assertNestedPropertyRead("prototype", "String");
assertPropertyRead("__defineGetter__");
}
}
@Test
public void globalPropertyRefError() {
evalWithTags("var o = {foo: 1};" +
"with (o) {" +
" foo = 42;" +
"}" +
"try {" +
" foo;" +
" throw new Error();" +
"}" +
"catch (e) {" +
" e instanceof ReferenceError;" +
"}", new Class[]{ReadPropertyTag.class, BinaryOperationTag.class});
enter(ReadPropertyTag.class, (e, p) -> {
p.input(assertGlobalObjectInput);
assertAttribute(e, KEY, "o");
}).exit();
enter(ReadPropertyTag.class, (e, p) -> {
assertAttribute(e, KEY, "foo");
p.input(assertGlobalObjectInput);
}).exitExceptional();
enter(BinaryOperationTag.class, (e, b) -> {
b.input(assertJSObjectInput);
enter(ReadPropertyTag.class, (e1, p) -> {
p.input(assertGlobalObjectInput);
assertAttribute(e1, KEY, "ReferenceError");
}).exit();
b.input(assertJSFunctionInput);
}).exit(assertReturnValue(true));
}
@Test
public void readNestedReadsInCalls() {
String src = "var exports = {" +
" bool: {" +
" enforcing: {" +
" trailingcomma: false" +
" }," +
" relaxing: {" +
" elision: true," +
" }" +
" }," +
" val: {" +
" esversion: 5" +
" }" +
"};" +
"Object.keys(exports.val)" +
" .concat(Object.keys(exports.bool.relaxing))" +
" .concat();";
evalWithTags(src, new Class[]{FunctionCallTag.class, ReadPropertyTag.class});
enter(FunctionCallTag.class, (e0, call0) -> {
enter(FunctionCallTag.class, (e1, call1) -> {
enter(FunctionCallTag.class, (e2, call2) -> {
assertPropertyRead("Object");
call2.input(assertJSFunctionInput);
assertPropertyRead("keys");
call2.input(assertJSFunctionInput);
assertNestedPropertyRead("val", "exports");
call2.input(assertTruffleObject);
}).exit();
call1.input(assertTruffleObject);
assertPropertyRead("concat");
call1.input(assertJSFunctionInput);
enter(FunctionCallTag.class, (e2, call2) -> {
assertPropertyRead("Object");
call2.input(assertJSFunctionInput);
assertPropertyRead("keys");
call2.input(assertJSFunctionInput);
assertNestedPropertyRead("relaxing", "bool", "exports");
call2.input(assertTruffleObject);
}).exit();
call1.input(assertTruffleObject);
}).exit();
call0.input(assertTruffleObject);
assertPropertyRead("concat");
call0.input(assertJSFunctionInput);
}).exit();
}
@Test
public void nestedPropertyReadBuggy() {
String src = "let foo = {bar: ()=>foo}; foo.bar(foo.bar).bar();";
evalWithTag(src, ReadPropertyTag.class);
assertPropertyRead("bar");
assertPropertyRead("bar");
assertPropertyRead("bar");
}
@Test
public void nestedPropertyReadOK() {
String src = "let foo = {bar: ()=>foo}; foo.bar(foo.bar).bar();";
evalWithTags(src, new Class<?>[]{ReadPropertyTag.class, DeclareTag.class});
assertPropertyRead("bar");
assertPropertyRead("bar");
assertPropertyRead("bar");
}
@Test
public void nonMaterializedApply() {
String src = "function foo(a) {\n" +
" return a;\n" +
"}\n" +
"function bar() {\n" +
" return foo.apply(undefined, arguments);\n" +
"}\n" +
"function baz(a) {\n" +
" return a;\n" +
"}\n" +
"baz(bar(1));";
evalWithTags(src, new Class[]{ReadPropertyTag.class}, new Class[]{});
enter(ReadPropertyTag.class, (e, pr) -> {
assertAttribute(e, KEY, "baz");
}).exit();
enter(ReadPropertyTag.class, (e, pr) -> {
assertAttribute(e, KEY, "bar");
}).exit();
enter(ReadPropertyTag.class, (e, pr) -> {
assertAttribute(e, KEY, "foo");
}).exit();
enter(ReadPropertyTag.class, (e, pr) -> {
assertAttribute(e, KEY, "apply");
}).exit();
}
@Test
public void materializedApply() {
String src = "function foo(a) {\n" +
" return a;\n" +
"}\n" +
"function bar() {\n" +
" return foo.apply(undefined, arguments);\n" +
"}\n" +
"function baz(a) {\n" +
" return a;\n" +
"}\n" +
"baz(bar(1));";
evalWithTag(src, ReadPropertyTag.class);
assertPropertyRead("baz");
assertPropertyRead("bar");
assertPropertyRead("foo");
assertPropertyRead("apply");
}
private void assertPropertyRead(String key) {
enter(ReadPropertyTag.class, (e, pr) -> {
assertAttribute(e, KEY, key);
pr.input(assertTruffleObject);
}).exit();
}
private void assertNestedPropertyRead(String key1, String key2) {
enter(ReadPropertyTag.class, (e, pr) -> {
assertAttribute(e, KEY, key1);
enter(ReadPropertyTag.class, (e1, pr1) -> {
assertAttribute(e1, KEY, key2);
pr1.input(assertTruffleObject);
}).exit();
pr.input(assertTruffleObject);
}).exit();
}
private void assertNestedPropertyRead(String key1, String key2, String key3) {
enter(ReadPropertyTag.class, (e, pr) -> {
assertAttribute(e, KEY, key1);
enter(ReadPropertyTag.class, (e1, pr1) -> {
assertAttribute(e1, KEY, key2);
enter(ReadPropertyTag.class, (e2, pr2) -> {
assertAttribute(e2, KEY, key3);
pr2.input(assertTruffleObject);
}).exit();
pr1.input(assertTruffleObject);
}).exit();
pr.input(assertTruffleObject);
}).exit();
}
}