Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b8834ff

Browse files
committed
add support for private fields in classes
1 parent fb90c2b commit b8834ff

9 files changed

Lines changed: 201 additions & 4 deletions

File tree

javascript/extractor/src/com/semmle/jcorn/Parser.java

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import com.semmle.jcorn.Identifiers.Dialect;
77
import com.semmle.jcorn.Options.AllowReserved;
8+
import com.semmle.jcorn.TokenType.Properties;
89
import com.semmle.js.ast.ArrayExpression;
910
import com.semmle.js.ast.ArrayPattern;
1011
import com.semmle.js.ast.ArrowFunctionExpression;
@@ -44,6 +45,7 @@
4445
import com.semmle.js.ast.IPattern;
4546
import com.semmle.js.ast.Identifier;
4647
import com.semmle.js.ast.IfStatement;
48+
import com.semmle.js.ast.FieldDefinition;
4749
import com.semmle.js.ast.ImportDeclaration;
4850
import com.semmle.js.ast.ImportDefaultSpecifier;
4951
import com.semmle.js.ast.ImportNamespaceSpecifier;
@@ -124,6 +126,7 @@ public class Parser {
124126
private boolean inModule;
125127
protected boolean inFunction;
126128
protected boolean inGenerator;
129+
protected boolean inClass;
127130
protected boolean inAsync;
128131
protected boolean inTemplateElement;
129132
protected int pos;
@@ -240,8 +243,8 @@ public Parser(Options options, String input, int startPos) {
240243
// Used to signify the start of a potential arrow function
241244
this.potentialArrowAt = -1;
242245

243-
// Flags to track whether we are in a function, a generator, an async function.
244-
this.inFunction = this.inGenerator = this.inAsync = false;
246+
// Flags to track whether we are in a function, a generator, an async function, a class.
247+
this.inFunction = this.inGenerator = this.inAsync = this.inClass = false;
245248
// Positions to delayed-check that yield/await does not exist in default parameters.
246249
this.yieldPos = this.awaitPos = 0;
247250
// Labels in scope.
@@ -651,6 +654,9 @@ protected Token getTokenFromCode(int code) {
651654
case 58:
652655
++this.pos;
653656
return this.finishToken(TokenType.colon);
657+
case 35:
658+
++this.pos;
659+
return this.finishToken(TokenType.pound);
654660
case 63:
655661
return this.readToken_question();
656662

@@ -2191,6 +2197,7 @@ protected Expression processExprListItem(Expression e) {
21912197
// identifiers.
21922198
protected Identifier parseIdent(boolean liberal) {
21932199
Position startLoc = this.startLoc;
2200+
boolean isPrivateField = liberal && this.eat(TokenType.pound);
21942201
if (liberal && this.options.allowReserved() == AllowReserved.NEVER) liberal = false;
21952202
String name = null;
21962203
if (this.type == TokenType.name) {
@@ -2199,9 +2206,9 @@ protected Identifier parseIdent(boolean liberal) {
21992206
&& (this.options.ecmaVersion() >= 6
22002207
|| inputSubstring(this.start, this.end).indexOf("\\") == -1))
22012208
this.raiseRecoverable(this.start, "The keyword '" + this.value + "' is reserved");
2202-
if (this.inGenerator && this.value.equals("yield"))
2209+
if (!isPrivateField && this.inGenerator && this.value.equals("yield"))
22032210
this.raiseRecoverable(this.start, "Can not use 'yield' as identifier inside a generator");
2204-
if (this.inAsync && this.value.equals("await"))
2211+
if (!isPrivateField && this.inAsync && this.value.equals("await"))
22052212
this.raiseRecoverable(
22062213
this.start, "Can not use 'await' as identifier inside an async function");
22072214
name = String.valueOf(this.value);
@@ -2213,6 +2220,12 @@ protected Identifier parseIdent(boolean liberal) {
22132220
this.unexpected();
22142221
}
22152222
this.next();
2223+
if (isPrivateField) {
2224+
if (!this.inClass) {
2225+
this.raiseRecoverable(this.start, "Cannot use private fields outside a class");
2226+
}
2227+
name = "#" + name;
2228+
}
22162229
Identifier node = new Identifier(new SourceLocation(startLoc), name);
22172230
return this.finishNode(node);
22182231
}
@@ -3127,6 +3140,8 @@ protected List<Expression> parseFunctionParams() {
31273140
// Parse a class declaration or literal (depending on the
31283141
// `isStatement` parameter).
31293142
protected Node parseClass(Position startLoc, boolean isStatement) {
3143+
boolean oldInClass = this.inClass;
3144+
this.inClass = true;
31303145
SourceLocation loc = new SourceLocation(startLoc);
31313146
this.next();
31323147
Identifier id = this.parseClassId(isStatement);
@@ -3145,6 +3160,8 @@ protected Node parseClass(Position startLoc, boolean isStatement) {
31453160
Node node;
31463161
if (isStatement) node = new ClassDeclaration(loc, id, superClass, classBody);
31473162
else node = new ClassExpression(loc, id, superClass, classBody);
3163+
3164+
this.inClass = oldInClass;
31483165
return this.finishNode(node);
31493166
}
31503167

javascript/extractor/src/com/semmle/jcorn/TokenType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ public void updateContext(Parser parser, TokenType prevType) {
8585
dot = new TokenType(new Properties(".")),
8686
questiondot = new TokenType(new Properties("?.")),
8787
question = new TokenType(new Properties("?").beforeExpr()),
88+
pound = new TokenType(kw("#")),
8889
arrow = new TokenType(new Properties("=>").beforeExpr()),
8990
template = new TokenType(new Properties("template")),
9091
invalidTemplate = new TokenType(new Properties("invalidTemplate")),

javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,10 @@ module DataFlow {
535535
*/
536536
pragma[noinline]
537537
predicate accesses(Node base, string p) { getBase() = base and getPropertyName() = p }
538+
539+
predicate isPrivateField() {
540+
getPropertyName().charAt(0) = "#" and getPropertyNameExpr() instanceof Label
541+
}
538542
}
539543

540544
/**
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import javascript
2+
3+
class Configuration extends DataFlow::Configuration {
4+
Configuration() { this = "ClassDataFlowTestingConfig" }
5+
6+
override predicate isSource(DataFlow::Node source) {
7+
source.getEnclosingExpr().(StringLiteral).getValue().toLowerCase() = "source"
8+
}
9+
10+
override predicate isSink(DataFlow::Node sink) {
11+
any(DataFlow::CallNode call | call.getCalleeName() = "sink").getAnArgument() = sink
12+
}
13+
}
14+
15+
query predicate dataflow(DataFlow::Node pred, DataFlow::Node succ) {
16+
any(Configuration c).hasFlow(pred, succ)
17+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import javascript
2+
3+
query string getAccessModifier(DataFlow::PropRef ref, Expr prop) {
4+
prop = ref.getPropertyNameExpr() and
5+
if ref.isPrivateField() then
6+
result = "Private"
7+
else
8+
result = "Public"
9+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
(function () {
2+
var source = "source";
3+
4+
class Foo {
5+
#priv = source;
6+
getPriv() {
7+
return this.#priv;
8+
}
9+
10+
getFalsePrivate() {
11+
return this["#priv"]; // not the same field as above. But we currently don't distinguish private and "public" fields.
12+
}
13+
}
14+
sink(new Foo().getPriv()); // NOT OK.
15+
16+
sink(new Foo().getFalsePrivate()); // OK [FP: Is FP because we do nothing special about dataflow for private fields.]
17+
})();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class Foo {
2+
#privDecl = 3;
3+
#if = "if"; // "keywords" are ok.
4+
reads() {
5+
var foo = this.#privUse;
6+
var bar = this["#publicComputed"]
7+
var baz = this.#if;
8+
}
9+
10+
equals(o) {
11+
return this.#privDecl === o.#privDecl;
12+
}
13+
14+
writes() {
15+
this.#privDecl = 4;
16+
this["#public"] = 5;
17+
}
18+
19+
#privSecond; // is a PropNode, not a PropRef. Doesn't matter.
20+
21+
["#publicField"] = 6;
22+
}

0 commit comments

Comments
 (0)