Ejemplo n.º 1
0
  public RenameProperty(Master input, AccessWithName expression, String newName) {
    super(input);
    this.newName = newName;

    if (!AstUtil.isName(newName)) {
      log.fatal("New name is invalid");
      return;
    }

    // if the expression is unreachable, don't bother
    //        if (input.getAccessedObjects(expression).isEmpty())
    //        	log.warn(expression.getName(), "Expression is unreachable");
    //    	if (expression.getDirectReceivers(input).isEmpty()) {
    //    	    log.warn(expression.getName(), "Expression is unreachable");
    //    	}

    String oldName = expression.getName();

    NodeFinder finder =
        new NodeFinder(
            input,
            IPropertyAccessNode.class,
            ANameExp.class,
            ANormalObjectLiteralProperty.class,
            ABinopExp.class);
    List<AccessWithName> nodesWithNewName = AccessFinder.getNamedAccesses(finder, newName);
    Set<ABinopExp> dynamicInExps = new HashSet<ABinopExp>();

    // find nodes with new name and dynamic in expressions
    for (ABinopExp exp : finder.getAllNodesOfType(ABinopExp.class)) {
      if (exp.getOp().kindPBinop() != EBinop.IN) continue;
      PExp left = exp.getLeft();
      if (!(left instanceof AConstExp)) {
        dynamicInExps.add(exp);
        continue; // non-constant node cannot be renamed
      }
      AConstExp leftc = (AConstExp) left;
      if (!(leftc.getConst() instanceof AStringConst)) continue; // int and boolean are safe
      AStringConst sc = (AStringConst) leftc.getConst();
      String name = Literals.parseStringLiteral(sc.getStringLiteral().getText());
      if (name.equals(newName)) nodesWithNewName.add(new ConstInExpAccess(sc, exp));
    }

    List<Access> propertySensitiveNodes = new ArrayList<Access>();
    propertySensitiveNodes.addAll(AccessFinder.getNamedAccesses(finder, oldName));
    ForInDynamicAccesses forinAccess = getForInDynamicAccesses(input, finder);
    for (ADynamicPropertyExp exp : forinAccess.getSafeExps()) {
      propertySensitiveNodes.add(new DynamicPropertyExpAccess(exp));
    }
    affected =
        FamilyClosure.compute(
            new RenamingFamily(input, oldName, forinAccess),
            propertySensitiveNodes,
            Collections.singleton(expression),
            Collections.<Host>emptySet());
    affectedObjects =
        ObjectHost.unwrap(CollectionUtil.filter(affected.getAffectedObjects(), ObjectHost.class));
    affectedScopes = CollectionUtil.filter(affected.getAffectedObjects(), ScopeHost.class);

    Set<IFunction> violatingNatives = new HashSet<IFunction>();
    for (AccessWithoutName access : getAffectedDynamicAccesses()) {
      if (input.isNativeCode(access.getNode())) continue;
      boolean safe = false;
      if (access instanceof DynamicPropertyExpAccess) {
        DynamicPropertyExpAccess dyn = (DynamicPropertyExpAccess) access;
        if (input.isDefinitelyArrayLookup(dyn.getExp())
            || forinAccess.getSafeExps().contains(dyn.getExp())) {
          safe = true;
        }
      }
      if (safe) continue;
      log.warn(
          access.getNode(),
          "Access %s might be affected, but cannot be updated.",
          access.getNode());
    }
    for (ADynamicPropertyExp exp : finder.getAllNodesOfType(ADynamicPropertyExp.class)) {
      if (input.isDefinitelyArrayLookup(exp) || forinAccess.getSafeExps().contains(exp)) continue;
      Set<ObjectValue> receivers = input.getReceivers(exp);
      if (CollectionUtil.intersects(receivers, affectedObjects))
        if (input.isNativeCode(exp)) {
          violatingNatives.add(exp.getAncestor(IFunction.class));
        } else {
          log.warn(exp, "Expression %s might be affected, but cannot be updated.", exp);
        }
    }
    // check for for-in loop conflicts
    for (AForInStmt forin : finder.getAllNodesOfType(AForInStmt.class)) {
      if (forinAccess.getSafeForIns().contains(forin)) {
        continue;
      }
      Set<ObjectValue> receivers = input.getAllPrototypes(input.getForInObjects(forin), true);
      if (CollectionUtil.intersects(receivers, affectedObjects)) {
        if (input.isNativeCode(forin)) {
          violatingNatives.add(forin.getAncestor(IFunction.class));
        } else {
          log.warn(forin, "Property name produced by for-in loop may change");
        }
      }
    }
    // check for dynamic 'in' expression conflicts
    for (ABinopExp exp : dynamicInExps) {
      if (input.isNativeCode(exp)) continue;
      Set<ObjectValue> objs = input.getInExpObjectArgs(exp);
      if (CollectionUtil.intersects(objs, affectedObjects))
        log.warn(exp, "The expression %s might be affected, but cannot be updated.", exp);
    }
    // check if new name clashes with existing properties
    for (AccessWithName node : this.getAffectedNames()) {
      if (input.isNativeCode(node.getNode())) continue;
      Set<ObjectValue> base = node.getBase(input, newName);
      if (!base.isEmpty()) {
        log.error(
            node.getNode(),
            "Renaming expression %s may clash with existing property of name %s.",
            node.getNode(),
            newName);
      }
      if (node instanceof AccessVariable) {
        // note: the check with getBase above took care of conflicting with-scopes and global object
        // properties
        AccessVariable varAccess = (AccessVariable) node;
        for (ScopeHost scope : varAccess.getSearchedScopes(input, oldName)) {
          if (scope.isGlobal()) {
            continue;
          }
          if (scope.getScope().getDeclaredVariables().contains(newName)) {
            log.error(
                node.getNode(),
                "Renaming expression %s will change resolution of variable %s.",
                node.getNode(),
                newName);
          }
        }
      }
    }
    // check if existing accesses with the new name are affected
    for (AccessWithName node : nodesWithNewName) {
      if (input.isNativeCode(node.getNode())) continue;
      Set<ObjectValue> receivers = node.getReceivers(input);
      if (CollectionUtil.intersects(receivers, affectedObjects)) {
        log.error(node.getNode(), "Expression %s may clash with renamed property.", node.getNode());
      } else if (node instanceof AccessVariable) {
        AccessVariable varAccess = (AccessVariable) node;
        if (CollectionUtil.intersects(
            new HashSet<ScopeHost>(varAccess.getSearchedScopes(input)), affectedScopes)) {
          log.error(
              node.getNode(), "Expression %s may clash with renamed property.", node.getNode());
        }
      }
    }
    // check for renaming native stuff in the harness files
    for (AccessWithName node : this.getAffectedNames()) {
      if (input.isNativeCode(node.getNode()))
        log.error("Native property %s cannot be renamed", node.getNode());
    }
    // check for renaming native stuff not in the harness files
    // XXX The difference between code in harness files and internal natives is quite
    // analysis-specific - can it be abstracted??
    for (NativeFunctionValue nativ : JSUtil.NATIVE_FUNCTIONS) {
      if (affectedObjects.contains(nativ.getFunctionPrototype())
          && nativ.getNativeMembers().contains(oldName))
        log.error("Native property %s.%s cannot be renamed.", nativ.getPrettyName(), oldName);
    }
    // check for renaming of toString, valueOf or constructor
    if (oldName.equals("toString") || oldName.equals("valueOf"))
      log.warn("Renaming %s may affect implicit coercions.", oldName);
    if (newName.equals("toString") || newName.equals("valueOf"))
      log.warn("Renaming to %s may affect implicit coersions", newName);
    if (oldName.equals("constructor") || newName.equals("constructor"))
      log.warn("Renaming property from/to 'constructor' may affect instanceof.");
    if (CollectionUtil.containsInstanceOf(affectedObjects, FunctionValue.class)
        && (oldName.equals("prototype") || newName.equals("prototype"))) {
      log.warn("Cannot rename 'prototype' property.");
    }
    boolean coercedStringsAreAffected =
        affectedObjects.contains(new CoercedPrimitiveObjectValue(StringValue.Instance));
    boolean isLength = oldName.equals("length") || newName.equals("length");
    if (coercedStringsAreAffected && isLength) {
      log.error("Cannot rename String.length property");
    }
    boolean arraysAreAffected =
        input
            .getAllPrototypes(affectedObjects, false)
            .contains(
                new UserFunctionValue(input.getHarnessNativeFunction("Array"), MainContext.Instance)
                    .getFunctionPrototype());
    if (arraysAreAffected && isLength) {
      log.error("Cannot rename Array.length property");
    }
    // check for calling a native function that performs dynamic property access
    violatingNatives.addAll(findNativeDynamicAccesses(affectedObjects));
    for (IFunction func : violatingNatives) {
      log.warn(
          "Renamed property may be dynamically accessed by native function %s",
          getNativeFunctionName(func));
    }
    checkEval(input);

    // TODO more conflict checks?
  }
Ejemplo n.º 2
0
 @Override
 public List<ScopeHost> getSearchedScopes(Master input, String name) {
   return Collections.singletonList(
       ScopeHost.fromScope(input.getScope(decl.getAncestor(ABody.class))));
 }