Skip to content

Commit

Permalink
Change CheckAccessControls to use a different definition of scoping…
Browse files Browse the repository at this point in the history
… that doesn't quite align with true JS scopes but better models the intent of access-controls.

This change is not expected to have visible effects at this time, but is a step towards separating constructor and class access-controls.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=209619585
  • Loading branch information
nreid260 authored and blickly committed Aug 27, 2018
1 parent 67ca821 commit 0268962
Showing 1 changed file with 63 additions and 32 deletions.
95 changes: 63 additions & 32 deletions src/com/google/javascript/jscomp/CheckAccessControls.java
Expand Up @@ -22,8 +22,7 @@
import com.google.common.collect.HashMultimap; import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback; import com.google.javascript.jscomp.NodeTraversal.Callback;
import com.google.javascript.jscomp.NodeTraversal.ScopedCallback;
import com.google.javascript.rhino.JSDocInfo; import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfo.Visibility; import com.google.javascript.rhino.JSDocInfo.Visibility;
import com.google.javascript.rhino.Node; import com.google.javascript.rhino.Node;
Expand All @@ -39,17 +38,15 @@
import javax.annotation.Nullable; import javax.annotation.Nullable;


/** /**
* A compiler pass that checks that the programmer has obeyed all the access * A compiler pass that checks that the programmer has obeyed all the access control restrictions
* control restrictions indicated by JSDoc annotations, like * indicated by JSDoc annotations, like {@code @private} and {@code @deprecated}.
* {@code @private} and {@code @deprecated}.
* *
* Because access control restrictions are attached to type information, this pass must run * <p>Because access control restrictions are attached to type information, this pass must run after
* after TypeInference, and InferJSDocInfo. * TypeInference, and InferJSDocInfo.
* *
* @author nicksantos@google.com (Nick Santos) * @author nicksantos@google.com (Nick Santos)
*/ */
class CheckAccessControls extends AbstractPostOrderCallback class CheckAccessControls implements Callback, HotSwapCompilerPass {
implements ScopedCallback, HotSwapCompilerPass {


static final DiagnosticType DEPRECATED_NAME = DiagnosticType.disabled( static final DiagnosticType DEPRECATED_NAME = DiagnosticType.disabled(
"JSC_DEPRECATED_VAR", "JSC_DEPRECATED_VAR",
Expand Down Expand Up @@ -176,31 +173,49 @@ public void hotSwapScript(Node scriptRoot, Node originalRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this); NodeTraversal.traverse(compiler, scriptRoot, this);
} }


@Override private void enterAccessControlScope(Node root) {
public void enterScope(NodeTraversal t) { @Nullable ObjectType scopeType = bestInstanceTypeForMethodOrCtor(root);
Node n = t.getScopeRoot();


if (isDeprecationScopeRoot(n)) { if (isMarkedDeprecated(root)) {
deprecationDepth++; deprecationDepth++;
} }

currentClassStack.push(scopeType);
if (isFunctionOrClass(n)) {
currentClassStack.push(bestInstanceTypeForMethodOrCtor(n));
}
} }


@Override private void exitAccessControlScope(Node root) {
public void exitScope(NodeTraversal t) { if (isMarkedDeprecated(root)) {
Node n = t.getScopeRoot();

if (isDeprecationScopeRoot(n)) {
deprecationDepth--; deprecationDepth--;
} }
currentClassStack.pop();
}


if (n.isFunction()) { /**
currentClassStack.pop(); * Maps {@code node} to the <em>primary</em> root of an access-control scope if it is some root,
} else if (n.isClass()) { * or {@code null} if it is a non-root of the scope.
currentClassStack.pop(); *
* <p>We define access-control scopes differently from {@code Scope}s because of mismatches
* between ECMAScript scoping and our AST structure (e.g. the `extends` clause of a CLASS). {@link
* NodeTraveral} is designed to walk the AST in ECMAScript scope order, which is not the pre-order
* traversal that we would prefer here. This requires us to treat access-control scopes as a
* forest with a primary root.
*
* <p>Each access-control scope corresponds to some "owner" JavaScript type which is used when
* computing access-controls. At this time, each also corresponds to an AST subtree.
*
* <p>Known mismatches:
*
* <ul>
* <li>CLASS extends clause: secondary root of the scope defined by the CLASS.
* </ul>
*/
@Nullable
private static Node primaryAccessControlScopeRootFor(Node node) {
if (isExtendsTarget(node)) {
return node.getParent();
} else if (isFunctionOrClass(node)) {
return node;
} else {
return null;
} }
} }


Expand Down Expand Up @@ -304,6 +319,16 @@ private static ObjectType instanceTypeFor(JSType type) {
return type.toMaybeObjectType(); return type.toMaybeObjectType();
} }


@Override
public boolean shouldTraverse(NodeTraversal traversal, Node node, Node parent) {
@Nullable Node accessControlRoot = primaryAccessControlScopeRootFor(node);
if (accessControlRoot != null) {
enterAccessControlScope(accessControlRoot);
}

return true;
}

@Override @Override
public void visit(NodeTraversal t, Node n, Node parent) { public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getToken()) { switch (n.getToken()) {
Expand Down Expand Up @@ -341,6 +366,11 @@ public void visit(NodeTraversal t, Node n, Node parent) {
checkPropertyVisibility(t, propRef); checkPropertyVisibility(t, propRef);
checkConstantProperty(t, propRef); checkConstantProperty(t, propRef);
} }

@Nullable Node accessControlRoot = primaryAccessControlScopeRootFor(n);
if (accessControlRoot != null) {
exitAccessControlScope(accessControlRoot);
}
} }


/** Checks the given NEW node to ensure that access restrictions are obeyed. */ /** Checks the given NEW node to ensure that access restrictions are obeyed. */
Expand Down Expand Up @@ -1007,11 +1037,7 @@ private boolean canAccessDeprecatedTypes(NodeTraversal t) {
* Returns whether this node roots a subtree under which references to deprecated constructs are * Returns whether this node roots a subtree under which references to deprecated constructs are
* allowed. * allowed.
*/ */
private static boolean isDeprecationScopeRoot(Node n) { private static boolean isMarkedDeprecated(Node n) {
if (!isFunctionOrClass(n)) {
return false;
}

return getDeprecationReason(NodeUtil.getBestJSDocInfo(n)) != null; return getDeprecationReason(NodeUtil.getBestJSDocInfo(n)) != null;
} }


Expand Down Expand Up @@ -1110,10 +1136,15 @@ private static ObjectType castToObject(@Nullable JSType type) {
return type == null ? null : type.toMaybeObjectType(); return type == null ? null : type.toMaybeObjectType();
} }


static boolean isFunctionOrClass(Node n) { private static boolean isFunctionOrClass(Node n) {
return n.isFunction() || n.isClass(); return n.isFunction() || n.isClass();
} }


private static boolean isExtendsTarget(Node node) {
Node parent = node.getParent();
return parent.isClass() && node.isSecondChildOf(parent);
}

@Nullable @Nullable
private JSType getTypeOfThis(Node scopeRoot) { private JSType getTypeOfThis(Node scopeRoot) {
if (scopeRoot.isRoot() || scopeRoot.isScript()) { if (scopeRoot.isRoot() || scopeRoot.isScript()) {
Expand Down

0 comments on commit 0268962

Please sign in to comment.