import java.util.Set;
import org.apache.poi.hssf.record.formula.eval.BlankEval;
+import org.apache.poi.hssf.record.formula.eval.ErrorEval;
import org.apache.poi.hssf.record.formula.eval.ValueEval;
import org.apache.poi.hssf.usermodel.HSSFCell;
throw new IllegalStateException("Call to endEvaluate without matching call to startEvaluate");
}
CellEvaluationFrame frame = _evaluationFrames.get(nFrames-1);
+ if (result == ErrorEval.CIRCULAR_REF_ERROR && nFrames > 1) {
+ // Don't cache a circular ref error result if this cell is not the top evaluated cell.
+ // A true circular ref error will propagate all the way around the loop. However, it's
+ // possible to have parts of the formula tree (/ parts of the loop) to evaluate to
+ // CIRCULAR_REF_ERROR, and that value not get used in the final cell result (see the
+ // unit tests for a simple example). Thus, the only CIRCULAR_REF_ERROR result that can
+ // safely be cached is that of the top evaluated cell.
+ return;
+ }
frame.updateFormulaResult(result);
}
tracker.acceptFormulaDependency(cce);
}
IEvaluationListener evalListener = _evaluationListener;
+ ValueEval result;
if (cce.getValue() == null) {
if (!tracker.startEvaluate(cce)) {
return ErrorEval.CIRCULAR_REF_ERROR;
}
try {
- ValueEval result;
Ptg[] ptgs = _workbook.getFormulaTokens(srcCell);
if (evalListener == null) {
if (isDebugLogEnabled()) {
String sheetName = getSheetName(sheetIndex);
CellReference cr = new CellReference(rowIndex, columnIndex);
- logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + cce.getValue().toString());
+ logDebug("Evaluated " + sheetName + "!" + cr.formatAsString() + " to " + result.toString());
}
- return cce.getValue();
+ // Usually (result === cce.getValue())
+ // But sometimes: (result==ErrorEval.CIRCULAR_REF_ERROR, cce.getValue()==null)
+ // When circular references are detected, the cache entry is only updated for
+ // the top evaluation frame
+ return result;
}
/**
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellValue;
+import org.apache.poi.ss.usermodel.ErrorConstants;
/**
* Tests HSSFFormulaEvaluator for its handling of cell formula circular references.
*
confirmCycleErrorCode(cellValue);
}
+
+ public void testIntermediateCircularReferenceResults_bug46898() {
+ HSSFWorkbook wb = new HSSFWorkbook();
+ HSSFSheet sheet = wb.createSheet("Sheet1");
+
+ HSSFRow row = sheet.createRow(0);
+
+ HSSFCell cellA1 = row.createCell(0);
+ HSSFCell cellB1 = row.createCell(1);
+ HSSFCell cellC1 = row.createCell(2);
+ HSSFCell cellD1 = row.createCell(3);
+ HSSFCell cellE1 = row.createCell(4);
+
+ cellA1.setCellFormula("IF(FALSE, 1+B1, 42)");
+ cellB1.setCellFormula("1+C1");
+ cellC1.setCellFormula("1+D1");
+ cellD1.setCellFormula("1+E1");
+ cellE1.setCellFormula("1+A1");
+
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+ CellValue cv;
+
+ // Happy day flow - evaluate A1 first
+ cv = fe.evaluate(cellA1);
+ assertEquals(Cell.CELL_TYPE_NUMERIC, cv.getCellType());
+ assertEquals(42.0, cv.getNumberValue(), 0.0);
+ cv = fe.evaluate(cellB1); // no circ-ref-error because A1 result is cached
+ assertEquals(Cell.CELL_TYPE_NUMERIC, cv.getCellType());
+ assertEquals(46.0, cv.getNumberValue(), 0.0);
+
+ // Show the bug - evaluate another cell from the loop first
+ fe.clearAllCachedResultValues();
+ cv = fe.evaluate(cellB1);
+ if (cv.getCellType() == ErrorEval.CIRCULAR_REF_ERROR.getErrorCode()) {
+ throw new AssertionFailedError("Identified bug 46898");
+ }
+ assertEquals(Cell.CELL_TYPE_NUMERIC, cv.getCellType());
+ assertEquals(46.0, cv.getNumberValue(), 0.0);
+
+ // start evaluation on another cell
+ fe.clearAllCachedResultValues();
+ cv = fe.evaluate(cellE1);
+ assertEquals(Cell.CELL_TYPE_NUMERIC, cv.getCellType());
+ assertEquals(43.0, cv.getNumberValue(), 0.0);
+
+
+ }
}