Exemple #1
0
    public EnumNameCallChecker(JProgram jprogram, TreeLogger logger) {
      this.logger = logger;
      this.enumNameMethod = jprogram.getIndexedMethod("Enum.name");
      this.enumToStringMethod = jprogram.getIndexedMethod("Enum.toString");
      this.classType = jprogram.getIndexedType("Class");
      this.enumType = jprogram.getIndexedType("Enum");
      this.stringType = jprogram.getIndexedType("String");

      /*
       * Find the correct version of enumValueOfMethod.
       *
       * Note: it doesn't work to check against a ref returned by
       * jprogram.getIndexedMethod("Enum.valueOf"), since there are 2 different
       * versions of Enum.valueOf in our jre emulation library, and the indexed
       * ref won't reliably flag the public instance, which is the one we want
       * here (i.e. Enum.valueOf(Class<T>,String)). The other version is
       * protected, but is called by the generated constructors for sub-classes
       * of Enum, and we don't want to warn for those cases.
       */
      JMethod foundMethod = null;
      List<JMethod> enumMethods = enumType.getMethods();
      for (JMethod enumMethod : enumMethods) {
        if ("valueOf".equals(enumMethod.getName())) {
          List<JParameter> jParameters = enumMethod.getParams();
          if (jParameters.size() == 2
              && jParameters.get(0).getType() == classType
              && jParameters.get(1).getType() == stringType) {
            foundMethod = enumMethod;
            break;
          }
        }
      }
      this.enumValueOfMethod = foundMethod;
    }
 private JLocal newExceptionVariable(SourceInfo sourceInfo) {
   return JProgram.createLocal(
       sourceInfo,
       "$e" + catchVariableIndex++,
       program.getTypeJavaLangObject(),
       false,
       currentMethodBody);
 }
Exemple #3
0
  /**
   * Transform a call to a pruned instance method (or static impl) into a call to the null method,
   * which will be used to replace <code>x</code>.
   */
  public static JMethodCall transformToNullMethodCall(JMethodCall x, JProgram program) {
    JExpression instance = x.getInstance();
    List<JExpression> args = x.getArgs();
    if (program.isStaticImpl(x.getTarget())) {
      instance = args.get(0);
      args = args.subList(1, args.size());
    } else {
      /*
       * We assert that method must be non-static, otherwise it would have been
       * rescued.
       */
      // assert !x.getTarget().isStatic();
      /*
       * HACK HACK HACK: ControlFlowAnalyzer has special hacks for dealing with
       * ClassLiterals, which causes the body of ClassLiteralHolder's clinit to
       * never be rescured. This in turn causes invalid references to static
       * methods, which violates otherwise good assumptions about compiler
       * operation.
       *
       * TODO: Remove this when ControlFlowAnalyzer doesn't special-case
       * CLH.clinit().
       */
      if (x.getTarget().isStatic() && instance == null) {
        instance = program.getLiteralNull();
      }
    }
    assert (instance != null);
    if (!instance.hasSideEffects()) {
      instance = program.getLiteralNull();
    }

    JMethodCall newCall =
        new JMethodCall(
            x.getSourceInfo(),
            instance,
            program.getNullMethod(),
            primitiveTypeOrNullTypeOrArray(program, x.getType()));
    // Retain the original arguments, they will be evaluated for side effects.
    for (JExpression arg : args) {
      if (arg.hasSideEffects()) {
        newCall.addArg(arg);
      }
    }
    return newCall;
  }
Exemple #4
0
  /** Returns an ast node representing the expression {@code expression != null}. */
  public static JExpression createOptimizedNotNullComparison(
      JProgram program, SourceInfo info, JExpression expression) {
    JReferenceType type = (JReferenceType) expression.getType();
    if (type.isNullType()) {
      return program.getLiteralBoolean(false);
    }

    if (!type.canBeNull()) {
      return createOptimizedMultiExpression(expression, program.getLiteralBoolean(true));
    }

    return new JBinaryOperation(
        info,
        program.getTypePrimitiveBoolean(),
        JBinaryOperator.NEQ,
        expression,
        program.getLiteralNull());
  }
  public static JProgram construct(
      TreeLogger logger, CompilationState state, Properties properties, String... entryPoints)
      throws UnableToCompleteException {
    PrecompileTaskOptions options = new PrecompileTaskOptionsImpl();
    options.setEnableAssertions(true);
    JProgram jprogram = AstConstructor.construct(logger, state, options, properties);

    // Add entry methods for entry points.
    for (String entryPoint : entryPoints) {
      JDeclaredType entryType = jprogram.getFromTypeMap(entryPoint);
      for (JMethod method : entryType.getMethods()) {
        if (method.isStatic() && JProgram.isClinit(method)) {
          jprogram.addEntryMethod(method);
        }
      }
    }
    // Tree is now ready to optimize.
    return jprogram;
  }
Exemple #6
0
 /** Return the smallest type that is is a subtype of the argument. */
 static JType primitiveTypeOrNullTypeOrArray(JProgram program, JType type) {
   if (type instanceof JArrayType) {
     JType leafType = primitiveTypeOrNullTypeOrArray(program, ((JArrayType) type).getLeafType());
     return program.getOrCreateArrayType(leafType, ((JArrayType) type).getDims());
   }
   if (type instanceof JPrimitiveType) {
     return type;
   }
   return JReferenceType.NULL_TYPE;
 }
 @Override
 protected boolean optimizeMethod(JProgram program, JMethod method) {
   program.addEntryMethod(findMainMethod(program));
   boolean didChange = false;
   // TODO(jbrosenberg): remove loop when Pruner/CFA interaction is perfect.
   while (TypeTightener.exec(program).didChange()) {
     didChange = true;
   }
   return didChange;
 }
  /**
   * Return the JSNI signature for a member. Leave off the return type for a method signature, so as
   * to match what a user would type in as a JsniRef.
   */
  private static String getJsniSignature(HasEnclosingType member, boolean wildcardParams) {
    if (member instanceof JField) {
      return ((JField) member).getName();
    }
    JMethod method = (JMethod) member;

    if (wildcardParams) {
      return method.getName() + "(" + JsniRef.WILDCARD_PARAM_LIST + ")";
    } else {
      return JProgram.getJsniSig(method, false);
    }
  }
Exemple #9
0
  /**
   * Transform a reference to a pruned instance field into a reference to the null field, which will
   * be used to replace <code>x</code>.
   */
  public static JFieldRef transformToNullFieldRef(JFieldRef x, JProgram program) {
    JExpression instance = x.getInstance();

    /*
     * We assert that field must be non-static if it's an rvalue, otherwise it
     * would have been rescued.
     */
    // assert !x.getField().isStatic();
    /*
     * HACK HACK HACK: ControlFlowAnalyzer has special hacks for dealing with
     * ClassLiterals, which causes the body of ClassLiteralHolder's clinit to
     * never be rescured. This in turn causes invalid references to static
     * methods, which violates otherwise good assumptions about compiler
     * operation.
     *
     * TODO: Remove this when ControlFlowAnalyzer doesn't special-case
     * CLH.clinit().
     */
    if (x.getField().isStatic() && instance == null) {
      instance = program.getLiteralNull();
    }

    assert instance != null;
    if (!instance.hasSideEffects()) {
      instance = program.getLiteralNull();
    }

    JFieldRef fieldRef =
        new JFieldRef(
            x.getSourceInfo(),
            instance,
            program.getNullField(),
            x.getEnclosingType(),
            primitiveTypeOrNullTypeOrArray(program, x.getType()));
    return fieldRef;
  }
Exemple #10
0
 @Override
 public boolean visit(JProgram program, Context ctx) {
   for (Iterator<JDeclaredType> it = program.getDeclaredTypes().iterator(); it.hasNext(); ) {
     JDeclaredType type = it.next();
     if (referencedTypes.contains(type)) {
       accept(type);
     } else {
       prunedMethods.addAll(type.getMethods());
       methodsWereRemoved(type.getMethods());
       fieldsWereRemoved(type.getFields());
       it.remove();
       madeChanges();
     }
   }
   return false;
 }
Exemple #11
0
    @Override
    public void endVisit(JLocalRef x, Context ctx) {
      JLocal originalLocal = x.getLocal();
      JLocal newLocal = newLocalsByOriginalLocal.get(originalLocal);
      if (newLocal == null) {
        newLocal =
            JProgram.createLocal(
                originalLocal.getSourceInfo(),
                originalLocal.getName(),
                originalLocal.getType(),
                originalLocal.isFinal(),
                methodBody);
        newLocalsByOriginalLocal.put(originalLocal, newLocal);
      }

      ctx.replaceMe(new JLocalRef(x.getSourceInfo(), newLocal));
    }
Exemple #12
0
  private OptimizerStats execImpl(OptimizerContext optimizerCtx) {
    OptimizerStats stats = new OptimizerStats(NAME);

    ControlFlowAnalyzer livenessAnalyzer = new ControlFlowAnalyzer(program);
    livenessAnalyzer.setForPruning();

    // SPECIAL: Immortal codegen types are never pruned
    traverseTypes(livenessAnalyzer, program.immortalCodeGenTypes);

    if (saveCodeGenTypes) {
      /*
       * SPECIAL: Some classes contain methods used by code generation later.
       * Unless those transforms have already been performed, we must rescue all
       * contained methods for later user.
       */
      traverseTypes(livenessAnalyzer, program.codeGenTypes);
    }
    livenessAnalyzer.traverseEverything();

    program.typeOracle.setInstantiatedTypes(livenessAnalyzer.getInstantiatedTypes());

    PruneVisitor pruner =
        new PruneVisitor(
            livenessAnalyzer.getReferencedTypes(),
            livenessAnalyzer.getLiveFieldsAndMethods(),
            optimizerCtx);
    pruner.accept(program);
    stats.recordModified(pruner.getNumMods());

    if (!pruner.didChange()) {
      return stats;
    }
    CleanupRefsVisitor cleaner =
        new CleanupRefsVisitor(
            livenessAnalyzer.getLiveFieldsAndMethods(),
            pruner.getPriorParametersByMethod(),
            optimizerCtx);
    cleaner.accept(program.getDeclaredTypes());
    optimizerCtx.incOptimizationStep();
    optimizerCtx.syncDeletedSubCallGraphsSince(
        optimizerCtx.getLastStepFor(NAME) + 1, prunedMethods);
    JavaAstVerifier.assertProgramIsConsistent(program);
    return stats;
  }
  private static void addMember(
      LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>> matchesBySig,
      HasEnclosingType member,
      String refSig) {
    LinkedHashMap<String, HasEnclosingType> matchesByFullSig = matchesBySig.get(refSig);
    if (matchesByFullSig == null) {
      matchesByFullSig = new LinkedHashMap<String, HasEnclosingType>();
      matchesBySig.put(refSig, matchesByFullSig);
    }

    String fullSig;
    if (member instanceof JField) {
      fullSig = ((JField) member).getName();
    } else {
      fullSig = JProgram.getJsniSig((JMethod) member);
    }

    matchesByFullSig.put(fullSig, member);
  }
Exemple #14
0
  /**
   * Choose an initial load sequence of split points for the specified program. Do so by identifying
   * split points whose code always load first, before any other split points. As a side effect,
   * modifies {@link com.google.gwt.core.client.impl.AsyncFragmentLoader#initialLoadSequence} in the
   * program being compiled.
   *
   * @throws UnableToCompleteException If the module specifies a bad load order
   */
  public static void pickInitialLoadSequence(
      TreeLogger logger, JProgram program, ConfigurationProperties config)
      throws UnableToCompleteException {
    SpeedTracerLogger.Event codeSplitterEvent =
        SpeedTracerLogger.start(
            CompilerEventType.CODE_SPLITTER, "phase", "pickInitialLoadSequence");
    TreeLogger branch =
        logger.branch(TreeLogger.TRACE, "Looking up initial load sequence for split points");
    LinkedHashSet<JRunAsync> asyncsInInitialLoadSequence = new LinkedHashSet<JRunAsync>();

    List<String> initialSequence = config.getStrings(PROP_INITIAL_SEQUENCE);
    for (String runAsyncReference : initialSequence) {
      JRunAsync runAsync = findRunAsync(runAsyncReference, program, branch);
      if (asyncsInInitialLoadSequence.contains(runAsync)) {
        branch.log(TreeLogger.ERROR, "Split point specified more than once: " + runAsyncReference);
      }
      asyncsInInitialLoadSequence.add(runAsync);
    }

    logInitialLoadSequence(logger, asyncsInInitialLoadSequence);
    installInitialLoadSequenceField(program, asyncsInInitialLoadSequence);
    program.setInitialAsyncSequence(asyncsInInitialLoadSequence);
    codeSplitterEvent.end();
  }
Exemple #15
0
  /**
   * Installs the initial load sequence into AsyncFragmentLoader.BROWSER_LOADER. The initializer
   * looks like this:
   *
   * <pre>
   * AsyncFragmentLoader BROWSER_LOADER = makeBrowserLoader(1, new int[]{});
   * </pre>
   *
   * The second argument (<code>new int[]</code>) gets replaced by an array corresponding to <code>
   * initialLoadSequence</code>.
   */
  private static void installInitialLoadSequenceField(
      JProgram program, LinkedHashSet<JRunAsync> initialLoadSequence) {
    // Arg 1 is initialized in the source as "new int[]{}".
    JMethodCall call = ReplaceRunAsyncs.getBrowserLoaderConstructor(program);
    JExpression arg1 = call.getArgs().get(1);
    assert arg1 instanceof JNewArray;
    JArrayType arrayType = program.getTypeArray(JPrimitiveType.INT);
    assert ((JNewArray) arg1).getArrayType() == arrayType;
    List<JExpression> initializers = new ArrayList<JExpression>(initialLoadSequence.size());

    // RunAsyncFramentIndex will later be replaced by the fragment the async is in.
    // TODO(rluble): this approach is not very clean, ideally the load sequence should be
    // installed AFTER code splitting when the fragment ids are known; rather than inserting
    // a placeholder in the AST and patching the ast later.
    for (JRunAsync runAsync : initialLoadSequence) {
      initializers.add(
          new JNumericEntry(
              call.getSourceInfo(), "RunAsyncFragmentIndex", runAsync.getRunAsyncId()));
    }
    JNewArray newArray =
        JNewArray.createArrayWithInitializers(
            arg1.getSourceInfo(), arrayType, Lists.newArrayList(initializers));
    call.setArg(1, newArray);
  }
Exemple #16
0
    private JMethodCall createClinitCall(JMethodCall x) {
      JDeclaredType targetType = x.getTarget().getEnclosingType().getClinitTarget();
      if (!getCurrentMethod().getEnclosingType().checkClinitTo(targetType)) {
        // Access from this class to the target class won't trigger a clinit
        return null;
      }
      if (program.isStaticImpl(x.getTarget()) && !x.getTarget().getEnclosingType().isJsoType()) {
        // No clinit needed; target is really a non-jso instance method.
        return null;
      }
      if (JProgram.isClinit(x.getTarget())) {
        // This is a clinit call, doesn't need another clinit
        return null;
      }

      JMethod clinit = targetType.getClinitMethod();

      // If the clinit is a non-native, empty body we can optimize it out here
      if (!clinit.isNative() && (((JMethodBody) clinit.getBody())).getStatements().size() == 0) {
        return null;
      }

      return new JMethodCall(x.getSourceInfo(), null, clinit);
    }
Exemple #17
0
  /**
   * Given the source code to a Java class named <code>test.EntryPoint</code>, compiles it with
   * emulated stack traces turned on and returns the JavaScript.
   */
  private JsProgram compileClass(String... lines) throws UnableToCompleteException {

    // Gather the Java source code to compile.

    final String code = Joiner.on("\n").join(lines);

    MockResourceOracle sourceOracle = new MockResourceOracle();
    sourceOracle.addOrReplace(
        new MockJavaResource("test.EntryPoint") {
          @Override
          public CharSequence getContent() {
            return code;
          }
        });
    sourceOracle.add(JavaAstConstructor.getCompilerTypes());

    PrecompileTaskOptions options = new PrecompileTaskOptionsImpl();
    options.setOutput(JsOutputOption.PRETTY);
    options.setRunAsyncEnabled(false);
    CompilerContext context =
        new CompilerContext.Builder()
            .options(options)
            .minimalRebuildCache(new MinimalRebuildCache())
            .build();

    ConfigurationProperties config =
        new ConfigurationProperties(Arrays.asList(recordFileNamesProp, recordLineNumbersProp));

    CompilationState state =
        CompilationStateBuilder.buildFrom(logger, context, sourceOracle.getResources(), null);
    JProgram jProgram = AstConstructor.construct(logger, state, options, config);
    jProgram.addEntryMethod(findMethod(jProgram, "onModuleLoad"));

    if (inline) {
      MethodInliner.exec(jProgram);
    }

    CatchBlockNormalizer.exec(jProgram);
    // Construct the JavaScript AST.

    // These passes are needed by GenerateJavaScriptAST.
    ComputeCastabilityInformation.exec(jProgram, false);
    ImplementCastsAndTypeChecks.exec(jProgram, false);
    ArrayNormalizer.exec(jProgram);

    StringTypeMapper typeMapper = new StringTypeMapper(jProgram);
    ResolveRuntimeTypeReferences.exec(jProgram, typeMapper, TypeOrder.FREQUENCY);
    Map<StandardSymbolData, JsName> symbolTable =
        new TreeMap<StandardSymbolData, JsName>(new SymbolData.ClassIdentComparator());

    BindingProperty stackMode = new BindingProperty("compiler.stackMode");
    stackMode.addDefinedValue(new ConditionNone(), "EMULATED");

    PermutationProperties properties =
        new PermutationProperties(
            Arrays.asList(
                new BindingProperties(
                    new BindingProperty[] {stackMode}, new String[] {"EMULATED"}, config)));

    JsProgram jsProgram = new JsProgram();
    JavaToJavaScriptMap jjsmap =
        GenerateJavaScriptAST.exec(
                logger, jProgram, jsProgram, context, typeMapper, symbolTable, properties)
            .getLeft();

    // Finally, run the pass we care about.
    JsStackEmulator.exec(jProgram, jsProgram, properties, jjsmap);

    return jsProgram;
  }
Exemple #18
0
 public void exec() {
   accept(jprogram.getIndexedMethod("Enum.name"));
 }
Exemple #19
0
 public EnumNameReplacer(JProgram jprogram, TreeLogger logger) {
   this.logger = logger;
   this.jprogram = jprogram;
   this.enumType = (JClassType) jprogram.getIndexedType("Enum");
   this.enumObfuscatedName = jprogram.getIndexedMethod("Enum.obfuscatedName");
 }
Exemple #20
0
  /**
   * Find a split point as designated in the {@link #PROP_INITIAL_SEQUENCE} configuration property.
   */
  public static JRunAsync findRunAsync(String refString, JProgram program, TreeLogger branch)
      throws UnableToCompleteException {
    SpeedTracerLogger.Event codeSplitterEvent =
        SpeedTracerLogger.start(CompilerEventType.CODE_SPLITTER, "phase", "findRunAsync");
    Multimap<String, JRunAsync> splitPointsByRunAsyncName =
        computeRunAsyncsByName(program.getRunAsyncs(), false);

    if (refString.startsWith("@")) {
      JsniRef jsniRef = JsniRef.parse(refString);
      if (jsniRef == null) {
        branch.log(
            TreeLogger.ERROR,
            "Badly formatted JSNI reference in " + PROP_INITIAL_SEQUENCE + ": " + refString);
        throw new UnableToCompleteException();
      }
      final String lookupErrorHolder[] = new String[1];
      JNode referent =
          JsniRefLookup.findJsniRefTarget(
              jsniRef,
              program,
              new JsniRefLookup.ErrorReporter() {
                @Override
                public void reportError(String error) {
                  lookupErrorHolder[0] = error;
                }
              });
      if (referent == null) {
        TreeLogger resolveLogger =
            branch.branch(TreeLogger.ERROR, "Could not resolve JSNI reference: " + jsniRef);
        resolveLogger.log(TreeLogger.ERROR, lookupErrorHolder[0]);
        throw new UnableToCompleteException();
      }

      if (!(referent instanceof JMethod)) {
        branch.log(TreeLogger.ERROR, "Not a method: " + referent);
        throw new UnableToCompleteException();
      }

      JMethod method = (JMethod) referent;
      String canonicalName = ReplaceRunAsyncs.getImplicitName(method);
      Collection<JRunAsync> splitPoints = splitPointsByRunAsyncName.get(canonicalName);
      if (splitPoints == null) {
        branch.log(TreeLogger.ERROR, "Method does not enclose a runAsync call: " + jsniRef);
        throw new UnableToCompleteException();
      }
      if (splitPoints.size() > 1) {
        branch.log(
            TreeLogger.ERROR,
            "Method includes multiple runAsync calls, "
                + "so it's ambiguous which one is meant: "
                + jsniRef);
        throw new UnableToCompleteException();
      }

      assert splitPoints.size() == 1;
      return splitPoints.iterator().next();
    }

    // Assume it's a raw class name
    Collection<JRunAsync> splitPoints = splitPointsByRunAsyncName.get(refString);
    if (splitPoints == null || splitPoints.size() == 0) {
      branch.log(TreeLogger.ERROR, "No runAsync call is labelled with class " + refString);
      throw new UnableToCompleteException();
    }
    if (splitPoints.size() > 1) {
      branch.log(
          TreeLogger.ERROR, "More than one runAsync call is labelled with class " + refString);
      throw new UnableToCompleteException();
    }
    assert splitPoints.size() == 1;
    JRunAsync result = splitPoints.iterator().next();
    codeSplitterEvent.end();

    return result;
  }
    @Override
    public void endVisit(JTryStatement x, Context ctx) {
      if (x.getCatchClauses().isEmpty()) {
        return;
      }

      SourceInfo catchInfo = x.getCatchClauses().get(0).getBlock().getSourceInfo();
      JLocal exceptionVariable = newExceptionVariable(x.getSourceInfo());
      JBlock newCatchBlock = new JBlock(catchInfo);

      {
        // $e = Exceptions.wrap($e)
        JMethodCall call = new JMethodCall(catchInfo, null, wrapMethod);
        call.addArg(new JLocalRef(catchInfo, exceptionVariable));
        newCatchBlock.addStmt(
            JProgram.createAssignmentStmt(
                catchInfo, new JLocalRef(catchInfo, exceptionVariable), call));
      }

      /*
       * Build up a series of if, else if statements to test the type of the
       * exception object against the types of the user's catch block. Each catch block might have
       * multiple types in Java 7.
       *
       * Go backwards so we can nest the else statements in the correct order!
       */
      // rethrow the current exception if no one caught it.
      JStatement cur = new JThrowStatement(catchInfo, new JLocalRef(catchInfo, exceptionVariable));
      for (int i = x.getCatchClauses().size() - 1; i >= 0; i--) {
        JTryStatement.CatchClause clause = x.getCatchClauses().get(i);
        JBlock block = clause.getBlock();
        JLocalRef arg = clause.getArg();
        List<JType> exceptionsTypes = clause.getTypes();
        catchInfo = block.getSourceInfo();

        // if ($e instanceof ArgType1 or $e instanceof ArgType2 ...) {
        //   var userVar = $e; <user code>
        // }

        // Handle the first Exception type.
        JExpression ifTest =
            new JInstanceOf(
                catchInfo,
                (JReferenceType) exceptionsTypes.get(0),
                new JLocalRef(catchInfo, exceptionVariable));
        // Handle the rest of the Exception types if any.
        for (int j = 1; j < exceptionsTypes.size(); j++) {
          JExpression orExp =
              new JInstanceOf(
                  catchInfo,
                  (JReferenceType) exceptionsTypes.get(j),
                  new JLocalRef(catchInfo, exceptionVariable));
          ifTest =
              new JBinaryOperation(
                  catchInfo, JPrimitiveType.BOOLEAN, JBinaryOperator.OR, ifTest, orExp);
        }
        JDeclarationStatement declaration =
            new JDeclarationStatement(catchInfo, arg, new JLocalRef(catchInfo, exceptionVariable));
        block.addStmt(0, declaration);
        // nest the previous as an else for me
        cur = new JIfStatement(catchInfo, ifTest, block, cur);
      }

      newCatchBlock.addStmt(cur);

      // Replace with a single catch block.
      x.getCatchClauses().clear();
      List<JType> newCatchTypes = new ArrayList<JType>(1);
      newCatchTypes.add(exceptionVariable.getType());
      x.getCatchClauses()
          .add(
              new JTryStatement.CatchClause(
                  newCatchTypes,
                  new JLocalRef(newCatchBlock.getSourceInfo(), exceptionVariable),
                  newCatchBlock));
    }
  /**
   * Look up a JSNI reference.
   *
   * @param ref The reference to look up
   * @param program The program to look up the reference in
   * @param errorReporter A callback used to indicate the reason for a failed JSNI lookup
   * @return The item referred to, or <code>null</code> if it could not be found. If the return
   *     value is <code>null</code>, <code>errorReporter</code> will have been invoked.
   */
  public static HasEnclosingType findJsniRefTarget(
      JsniRef ref, JProgram program, JsniRefLookup.ErrorReporter errorReporter) {
    String className = ref.className();
    JType type = null;
    if (!className.equals("null")) {
      type = program.getTypeFromJsniRef(className);
      if (type == null) {
        errorReporter.reportError("Unresolvable native reference to type '" + className + "'");
        return null;
      }
    }

    if (!ref.isMethod()) {
      // look for a field
      String fieldName = ref.memberName();
      if (type == null) {
        if (fieldName.equals("nullField")) {
          return program.getNullField();
        }

      } else if (fieldName.equals(JsniRef.CLASS)) {
        JClassLiteral lit = program.getLiteralClass(type);
        return lit.getField();

      } else if (type instanceof JPrimitiveType) {
        errorReporter.reportError("May not refer to fields on primitive types");
        return null;

      } else if (type instanceof JArrayType) {
        errorReporter.reportError("May not refer to fields on array types");
        return null;

      } else {
        for (JField field : ((JDeclaredType) type).getFields()) {
          if (field.getName().equals(fieldName)) {
            return field;
          }
        }
      }

      errorReporter.reportError(
          "Unresolvable native reference to field '" + fieldName + "' in type '" + className + "'");
      return null;
    } else if (type instanceof JPrimitiveType) {
      errorReporter.reportError("May not refer to methods on primitive types");
      return null;
    } else {
      // look for a method
      LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>> matchesBySig =
          new LinkedHashMap<String, LinkedHashMap<String, HasEnclosingType>>();
      String methodName = ref.memberName();
      String jsniSig = ref.memberSignature();
      if (type == null) {
        if (jsniSig.equals("nullMethod()")) {
          return program.getNullMethod();
        }
      } else {
        findMostDerivedMembers(matchesBySig, (JDeclaredType) type, ref.memberName(), true);
        LinkedHashMap<String, HasEnclosingType> matches = matchesBySig.get(jsniSig);
        if (matches != null && matches.size() == 1) {
          /*
           * Backward compatibility: allow accessing bridge methods with full
           * qualification
           */
          return matches.values().iterator().next();
        }

        removeSyntheticMembers(matchesBySig);
        matches = matchesBySig.get(jsniSig);
        if (matches != null && matches.size() == 1) {
          return matches.values().iterator().next();
        }
      }

      // Not found; signal an error
      if (matchesBySig.isEmpty()) {
        errorReporter.reportError(
            "Unresolvable native reference to method '"
                + methodName
                + "' in type '"
                + className
                + "'");
        return null;
      } else {
        StringBuilder suggestList = new StringBuilder();
        String comma = "";
        // use a TreeSet to sort the near matches
        TreeSet<String> almostMatchSigs = new TreeSet<String>();
        for (String sig : matchesBySig.keySet()) {
          if (matchesBySig.get(sig).size() == 1) {
            almostMatchSigs.add(sig);
          }
        }
        for (String almost : almostMatchSigs) {
          suggestList.append(comma + "'" + almost + "'");
          comma = ", ";
        }
        errorReporter.reportError(
            "Unresolvable native reference to method '"
                + methodName
                + "' in type '"
                + className
                + "' (did you mean "
                + suggestList.toString()
                + "?)");
        return null;
      }
    }
  }
Exemple #23
0
    // Arguments for pruned parameters will be pushed right into a multiexpression that will be
    // evaluated with the next arg, e.g. m(arg1, (prunnedArg2, prunnedArg3, arg4)).
    private void maybeReplaceForPrunedParameters(JMethodCall x, Context ctx) {
      if (!priorParametersByMethod.containsKey(x.getTarget())) {
        // No parameter was pruned.
        return;
      }

      JMethodCall replacementCall = x.cloneWithoutParameters();

      assert !x.getTarget().canBePolymorphic();
      List<JParameter> originalParams = priorParametersByMethod.get(x.getTarget());

      // The method and the call agree in the number of parameters.
      assert originalParams.size() == x.getArgs().size();

      // Traverse the call arguments left to right.
      SourceInfo sourceInfo = x.getSourceInfo();
      JMultiExpression unevaluatedArgumentsForPrunedParameters = new JMultiExpression(sourceInfo);
      List<JExpression> args = x.getArgs();
      for (int currentArgumentIndex = 0;
          currentArgumentIndex < args.size();
          ++currentArgumentIndex) {
        JExpression arg = args.get(currentArgumentIndex);

        // If the parameter was not pruned .
        if (referencedNonTypes.contains(originalParams.get(currentArgumentIndex))) {
          // Add the current argument to the list of unevaluated arguments and pass the multi
          // expression to the call.
          unevaluatedArgumentsForPrunedParameters.addExpressions(arg);
          replacementCall.addArg(unevaluatedArgumentsForPrunedParameters);
          // Reset the accumulating multi expression.
          unevaluatedArgumentsForPrunedParameters = new JMultiExpression(sourceInfo);
        } else if (arg.hasSideEffects()) {
          // If the argument was pruned and has sideffects accumulate it; otherwise discard.
          unevaluatedArgumentsForPrunedParameters.addExpressions(arg);
        }
      }

      if (unevaluatedArgumentsForPrunedParameters.isEmpty()) {
        // We are done, all (side effectful) parameters have been evaluated.
        ctx.replaceMe(replacementCall);
        return;
      }

      // If the last few parameters where pruned, we need to evaluate the (side effectful) arguments
      // for those parameters.
      if (replacementCall.getArgs().isEmpty()) {
        // All parameters have been pruned, replace by (prunedArg1, ..., prunedArgn, m()).
        unevaluatedArgumentsForPrunedParameters.addExpressions(replacementCall);
        ctx.replaceMe(unevaluatedArgumentsForPrunedParameters);
        return;
      }
      // Some parameters have been pruned from the end, replace by
      // m(arg1,..., (lastArg = lastUnprunedArg, remainingArgs, lastArg))
      JExpression lastArg = Iterables.getLast(replacementCall.getArgs());
      JLocal tempVar =
          createTempLocal(
              sourceInfo,
              Iterables.getLast(Iterables.filter(originalParams, Predicates.in(referencedNonTypes)))
                  .getType());
      unevaluatedArgumentsForPrunedParameters.addExpressions(
          0,
          JProgram.createAssignment(
              lastArg.getSourceInfo(), new JLocalRef(sourceInfo, tempVar), lastArg));
      unevaluatedArgumentsForPrunedParameters.addExpressions(new JLocalRef(sourceInfo, tempVar));
      replacementCall.setArg(
          replacementCall.getArgs().size() - 1, unevaluatedArgumentsForPrunedParameters);
      ctx.replaceMe(replacementCall);
    }