Skip to content

Commit

Permalink
Add configuration to forcibly stop the partial analysis by throwing a…
Browse files Browse the repository at this point in the history
… ExcessiveComplexityException

The PartialEvaluator will throw if an instruction is visited more than `stopAnalysisAfterNEvaluations` times (i.e. the analysis takes too long). (An `ExcessiveComplexityException` which will be thrown)

`stopAnalysisAfterNEvaluations` is set to `-1` to not stop by default.

Also adds a `PartialEvaluator.Builder` to nicely build with setting this parameter.
  • Loading branch information
titze authored and mrjameshamilton committed Apr 30, 2021
1 parent 9472ea1 commit 81be970
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 2 deletions.
34 changes: 34 additions & 0 deletions src/proguard/evaluation/ExcessiveComplexityException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* ProGuardCORE -- library to process Java bytecode.
*
* Copyright (c) 2002-2020 Guardsquare NV
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package proguard.evaluation;

/**
* Represents an exception during partial evaluation when a single instruction would be visited more than {@link PartialEvaluator#stopAnalysisAfterNEvaluations(int)} times.
* In this case, the analysis will forcibly stop by throwing this exception.
*
* @author Dennis Titze
*/
public class ExcessiveComplexityException
extends RuntimeException
{
public ExcessiveComplexityException(String message)
{
super(message);
}
}

137 changes: 135 additions & 2 deletions src/proguard/evaluation/PartialEvaluator.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ public class PartialEvaluator
public static boolean DEBUG_RESULTS = DEBUG;
//*/

private static final int MAXIMUM_EVALUATION_COUNT = 5;
// The analysis will generalize stack/vars after visiting an instruction this many times.
private static final int GENERALIZE_AFTER_N_EVALUATIONS = 5;
// If the analysis visits an instruction this many times (this can happen e.g. for big switches),
// the analysis of this method is forcibly stopped and a ExcessiveComplexityException is thrown.
// By default (value set to -1), the analysis is not forcibly stopped.
private int stopAnalysisAfterNEvaluations = -1;

public static final int NONE = -2;
public static final int AT_METHOD_ENTRY = -1;
Expand Down Expand Up @@ -205,6 +210,120 @@ private PartialEvaluator(ValueFactory valueFactory,
callingInstructionBlockStack;
}

/**
* Builds this PartialEvaluator using the (partly) filled Builder.
*/
private PartialEvaluator(Builder builder)
{
this.valueFactory = builder.valueFactory == null ? new BasicValueFactory(): builder.valueFactory;
this.invocationUnit = builder.invocationUnit == null ? new BasicInvocationUnit(valueFactory) : builder.invocationUnit;
this.evaluateAllCode = builder.evaluateAllCode;
this.extraInstructionVisitor = builder.extraInstructionVisitor;
this.branchUnit = builder.branchUnit == null ? ( evaluateAllCode ?
new BasicBranchUnit() :
new TracedBranchUnit())
: builder.branchUnit;
this.branchTargetFinder = builder.branchTargetFinder == null ? new BranchTargetFinder() : builder.branchTargetFinder;
this.callingInstructionBlockStack = builder.callingInstructionBlockStack == null ? this.instructionBlockStack : builder.callingInstructionBlockStack;
this.stopAnalysisAfterNEvaluations = builder.stopAnalysisAfterNEvaluations;
}

public static class Builder {
private ValueFactory valueFactory;
private InvocationUnit invocationUnit;
private boolean evaluateAllCode = true;
private InstructionVisitor extraInstructionVisitor;
private BasicBranchUnit branchUnit;
private BranchTargetFinder branchTargetFinder;
private java.util.Stack callingInstructionBlockStack;
private int stopAnalysisAfterNEvaluations = -1; // disabled by default

public static Builder create()
{
return new Builder();
}
private Builder() {}

public PartialEvaluator build()
{
return new PartialEvaluator(this);
}

/**
* the value factory that will create all values during evaluation.
*/
public Builder setValueFactory(ValueFactory valueFactory)
{
this.valueFactory = valueFactory;
return this;
}

/**
* The invocation unit that will handle all communication with other fields and methods.
*/
public Builder setInvocationUnit(InvocationUnit invocationUnit)
{
this.invocationUnit = invocationUnit;
return this;
}

/**
* Specifies whether all casts, branch targets, and exceptionhandlers should be evaluated,
* even if they are unnecessary or unreachable.
*/
public Builder setEvaluateAllCode(boolean evaluateAllCode)
{
this.evaluateAllCode = evaluateAllCode;
return this;
}

/**
* an optional extra visitor for all instructions right before they are executed.
*/
public Builder setExtraInstructionVisitor(InstructionVisitor extraInstructionVisitor)
{
this.extraInstructionVisitor = extraInstructionVisitor;
return this;
}

/**
* The branch unit that will handle all branches.
*/
public Builder setBranchUnit(BasicBranchUnit branchUnit)
{
this.branchUnit = branchUnit;
return this;
}

/**
* The utility class that will find all branches.
*/
public Builder setBranchTargetFinder(BranchTargetFinder branchTargetFinder)
{
this.branchTargetFinder = branchTargetFinder;
return this;
}

/**
* the stack of instruction blocks to be evaluated.
*/
public Builder setCallingInstructionBlockStack(java.util.Stack callingInstructionBlockStack)
{
this.callingInstructionBlockStack = callingInstructionBlockStack;
return this;
}

/**
* The analysis of one method will forcibly stop (throwing a ExcessiveComplexityException)
* after this many evaluations of a single instruction.
*/
public Builder stopAnalysisAfterNEvaluations(int stopAnalysisAfterNEvaluations)
{
this.stopAnalysisAfterNEvaluations = stopAnalysisAfterNEvaluations;
return this;
}
}


// Implementations for AttributeVisitor.

Expand Down Expand Up @@ -814,10 +933,15 @@ private void evaluateSingleInstructionBlock(Clazz clazz,

// See if this instruction has been evaluated an excessive number
// of times.
if (evaluationCount >= MAXIMUM_EVALUATION_COUNT)
if (evaluationCount >= GENERALIZE_AFTER_N_EVALUATIONS)
{
if (DEBUG) System.out.println("Generalizing current context after "+evaluationCount+" evaluations");

if (stopAnalysisAfterNEvaluations != -1 && evaluationCount >= stopAnalysisAfterNEvaluations)
{
throw new ExcessiveComplexityException("Stopping evaluation after " + evaluationCount + " evaluations.");
}

// Continue, but generalize the current context.
// Note that the most recent variable values have to remain
// last in the generalizations, for the sake of the ret
Expand Down Expand Up @@ -1422,6 +1546,15 @@ private void generalizeVariables(int startOffset,
}
}

/**
* It the analysis visits an instruction this many times (this can happen e.g. for big switches),
* the analysis of this method is forcibly stopped and a ExcessiveComplexityException is thrown.
*/
public PartialEvaluator stopAnalysisAfterNEvaluations(int stopAnalysisAfterNEvaluations)
{
this.stopAnalysisAfterNEvaluations = stopAnalysisAfterNEvaluations;
return this;
}

/**
* This class represents an instruction block that has to be executed,
Expand Down

0 comments on commit 81be970

Please sign in to comment.