Fix prototype contamination bug (described in added test).

When someone writes: 
MyFunction.prototype.myMethod = ...
we want "myMethod" to be accessible only for objects of type "MyFunction".

- Change how generated ctors look like - now they have a private prototype object into which methods can be added
- Change the PropertyReadExpander to have a different handling when reading the "prototype" field, avoid performing a loop on the prototype chain, so that the points to set of fetched field will be accurate (and allow us to set only the fields of it)


git-svn-id: https://wala.svn.sourceforge.net/svnroot/wala/trunk@3959 f5eafffb-2e1d-0410-98e4-8ec43c5233c4
This commit is contained in:
yinnon_haviv 2010-10-02 19:30:03 +00:00
parent a0fb3b089f
commit 38f8a44044
5 changed files with 100 additions and 13 deletions

View File

@ -0,0 +1,28 @@
function A(){
}
A.prototype.foo = function foo_of_A(){
console.log("foo_of_A");
}
function B(){
}
B.prototype.foo = function foo_of_B(){
console.log("foo_of_B");
}
function test1(){
var a = new A
console.log("calling foo_of_A");
a.foo()
}
function test2(){
var b = new B
console.log("calling foo_of_B");
b.foo()
}
test1()
test2()

View File

@ -243,4 +243,21 @@ public abstract class TestSimpleCallGraphShape extends TestJSCallGraphShape {
verifyGraphAssertions(CG, assertionsForMultivar);
}
private static final Object[][] assertionsForPrototypeContamination = new Object[][] {
new Object[] { ROOT, new String[] { "tests/prototype_contamination_bug.js" } },
new Object[] { "suffix:test1",
new String[] { "suffix:foo_of_A"} },
new Object[] { "suffix:test2",
new String[] { "suffix:foo_of_B"} }
};
@Test public void testProtoypeContamination() throws IOException, IllegalArgumentException, CancelException {
CallGraph CG = Util.makeScriptCG("tests", "prototype_contamination_bug.js");
verifyGraphAssertions(CG, assertionsForPrototypeContamination);
verifyNoEdges(CG, "suffix:test1", "suffix:foo_of_B");
verifyNoEdges(CG, "suffix:test2", "suffix:foo_of_A");
}
}

View File

@ -349,11 +349,11 @@ public class JavaScriptConstructTargetSelector implements MethodTargetSelector {
S.addStatement(insts.NewInstruction(5, NewSiteReference.make(S.getNextProgramCounter(), cls.getReference())));
S.addStatement(insts.PutInstruction(5, 4, "prototype"));
S.getNextProgramCounter();
S.addStatement(insts.NewInstruction(7, NewSiteReference.make(S.getNextProgramCounter(), JavaScriptTypes.Object)));
S.addStatement(insts.PutInstruction(7, 4, "prototype"));
S.getNextProgramCounter();
S.addStatement(insts.PutInstruction(5, 7, "prototype"));
S.getNextProgramCounter();

View File

@ -81,17 +81,36 @@ public class PropertyReadExpander extends CAstRewriter<PropertyReadExpander.Rewr
private CAstNode makeConstRead(CAstNode receiver, CAstNode element, RewriteContext context) {
String receiverTemp = TEMP_NAME + (readTempCounter++);
String elt = (String) element.getValue();
if (context.inAssignment()) {
context.setAssign(Ast.makeNode(CAstNode.VAR, Ast.makeConstant(receiverTemp)), Ast.makeConstant(elt));
if (elt.equals("prototype")){
return Ast.makeNode(CAstNode.BLOCK_EXPR,
Ast.makeNode(CAstNode.OBJECT_REF,
receiver,
Ast.makeConstant("prototype")));
} else {
if (context.inAssignment()) {
context.setAssign(Ast.makeNode(CAstNode.VAR, Ast.makeConstant(receiverTemp)), Ast.makeConstant(elt));
}
return Ast.makeNode(CAstNode.BLOCK_EXPR,
Ast.makeNode(CAstNode.DECL_STMT,
Ast.makeConstant(new InternalCAstSymbol(receiverTemp,false, false)),
receiver),
Ast.makeNode(CAstNode.LOOP,
Ast.makeNode(CAstNode.UNARY_EXPR,
CAstOperator.OP_NOT,
Ast.makeNode(CAstNode.IS_DEFINED_EXPR,
Ast.makeNode(CAstNode.VAR, Ast.makeConstant(receiverTemp)),
Ast.makeConstant(elt))),
Ast.makeNode(CAstNode.ASSIGN,
Ast.makeNode(CAstNode.VAR, Ast.makeConstant(receiverTemp)),
Ast.makeNode(CAstNode.OBJECT_REF,
Ast.makeNode(CAstNode.VAR, Ast.makeConstant(receiverTemp)),
Ast.makeConstant("prototype")))),
Ast.makeNode(CAstNode.OBJECT_REF,
Ast.makeNode(CAstNode.VAR, Ast.makeConstant(receiverTemp)),
Ast.makeConstant(elt)));
}
return Ast.makeNode(CAstNode.BLOCK_EXPR, Ast.makeNode(CAstNode.DECL_STMT, Ast.makeConstant(new InternalCAstSymbol(receiverTemp,
false, false)), receiver), Ast.makeNode(CAstNode.LOOP, Ast.makeNode(CAstNode.UNARY_EXPR, CAstOperator.OP_NOT, Ast.makeNode(
CAstNode.IS_DEFINED_EXPR, Ast.makeNode(CAstNode.VAR, Ast.makeConstant(receiverTemp)), Ast.makeConstant(elt))), Ast
.makeNode(CAstNode.ASSIGN, Ast.makeNode(CAstNode.VAR, Ast.makeConstant(receiverTemp)), Ast.makeNode(CAstNode.OBJECT_REF,
Ast.makeNode(CAstNode.VAR, Ast.makeConstant(receiverTemp)), Ast.makeConstant("prototype")))), Ast.makeNode(
CAstNode.OBJECT_REF, Ast.makeNode(CAstNode.VAR, Ast.makeConstant(receiverTemp)), Ast.makeConstant(elt)));
}
private CAstNode makeVarRead(CAstNode receiver, CAstNode element, RewriteContext context) {

View File

@ -104,6 +104,29 @@ public abstract class TestCallGraphShape extends WalaTestCase {
}
}
/**
* Verifies that non of the nodes that match the source description has an edge to any of the nodes that match the destination
* description. (Used for checking for false connections in the callgraph)
*
* @param CG
* @param sourceDescription
* @param destDescription
*/
protected void verifyNoEdges(CallGraph CG, String sourceDescription, String destDescription) {
Collection sources = getNodes(CG, sourceDescription);
Collection dests = getNodes(CG, destDescription);
for (Object source : sources) {
for (Object dest : dests) {
for (Iterator<CGNode> i = CG.getSuccNodes((CGNode) source); i.hasNext();) {
if (i.next().equals(dest)) {
Assert.fail("Found a link from " + source + " to " + dest);
}
}
}
}
}
protected static final Object ROOT = new Object();
protected abstract Collection getNodes(CallGraph CG, String functionIdentifier);