001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.bcel.verifier.statics; 020 021import java.util.ArrayList; 022import java.util.List; 023import java.util.Map; 024import java.util.NavigableMap; 025import java.util.TreeMap; 026 027import org.apache.bcel.generic.Type; 028import org.apache.bcel.verifier.exc.LocalVariableInfoInconsistentException; 029 030/** 031 * A utility class holding the information about the name and the type of a local variable in a given slot (== index). 032 * This information often changes in course of byte code offsets. 033 */ 034public class LocalVariableInfo { 035 036 /** 037 * A contiguous, inclusive range of bytecode offsets sharing one variable name and one type. 038 */ 039 private static final class Range { 040 private final int start; 041 private final int end; // inclusive 042 private final String name; 043 private final Type type; 044 045 Range(final int start, final int end, final String name, final Type type) { 046 this.start = start; 047 this.end = end; 048 this.name = name; 049 this.type = type; 050 } 051 } 052 053 /** 054 * The database of ranges, keyed by their start offset. Invariant: the stored ranges never overlap each other; additions overlapping an existing range 055 * with consistent information are coalesced into it, inconsistent ones are rejected. Storing ranges instead of one entry per offset keeps the work and 056 * memory proportional to the number of LocalVariableTable entries: the startPc and length fields are attacker-controlled in a malicious class file and 057 * would otherwise amplify each 10-byte table entry into up to 65,536 hashtable operations (CWE-407). 058 */ 059 private final NavigableMap<Integer, Range> ranges = new TreeMap<>(); 060 061 /** 062 * Constructs a new LocalVariableInfo. 063 */ 064 public LocalVariableInfo() { 065 } 066 067 /** 068 * Adds some information about this local variable (slot). 069 * 070 * @param name variable name. 071 * @param startPc Range in which the variable is valid. 072 * @param length length of ... 073 * @param type variable type. 074 * @throws LocalVariableInfoInconsistentException Thrown if the new information conflicts with already gathered information. 075 */ 076 public void add(final String name, final int startPc, final int length, final Type type) throws LocalVariableInfoInconsistentException { 077 final int endPc = startPc + length; // incl/incl-notation! 078 int mergedStart = startPc; 079 int mergedEnd = endPc; 080 // Only ranges starting at or before endPc can overlap [startPc, endPc]; since stored ranges never overlap each other, the first candidate is the 081 // last range starting at or before startPc. 082 Integer from = ranges.floorKey(startPc); 083 if (from == null) { 084 from = Integer.valueOf(startPc); 085 } 086 final List<Integer> merged = new ArrayList<>(); 087 for (final Map.Entry<Integer, Range> entry : ranges.subMap(from, true, Integer.valueOf(endPc), true).entrySet()) { 088 final Range range = entry.getValue(); 089 if (range.end < startPc) { 090 continue; // does not overlap. 091 } 092 final int offset = Math.max(startPc, range.start); 093 if (!range.name.equals(name)) { 094 throw new LocalVariableInfoInconsistentException( 095 "At bytecode offset '" + offset + "' a local variable has two different names: '" + range.name + "' and '" + name + "'."); 096 } 097 if (!range.type.equals(type)) { 098 throw new LocalVariableInfoInconsistentException( 099 "At bytecode offset '" + offset + "' a local variable has two different types: '" + range.type + "' and '" + type + "'."); 100 } 101 // Consistent overlap: coalesce, so the database stays proportional to the number of disjoint ranges. 102 mergedStart = Math.min(mergedStart, range.start); 103 mergedEnd = Math.max(mergedEnd, range.end); 104 merged.add(entry.getKey()); 105 } 106 merged.forEach(ranges::remove); 107 ranges.put(Integer.valueOf(mergedStart), new Range(mergedStart, mergedEnd, name, type)); 108 } 109 110 /** 111 * Returns the name of the local variable that uses this local variable slot at the given bytecode offset. Care for 112 * legal bytecode offsets yourself, otherwise the return value might be wrong. May return 'null' if nothing is known 113 * about the type of this local variable slot at the given bytecode offset. 114 * 115 * @param offset bytecode offset. 116 * @return The name of the local variable that uses this local variable slot at the given bytecode offset. 117 */ 118 public String getName(final int offset) { 119 final Range range = lookup(offset); 120 return range != null ? range.name : null; 121 } 122 123 /** 124 * Returns the type of the local variable that uses this local variable slot at the given bytecode offset. Care for 125 * legal bytecode offsets yourself, otherwise the return value might be wrong. May return 'null' if nothing is known 126 * about the type of this local variable slot at the given bytecode offset. 127 * 128 * @param offset bytecode offset. 129 * @return The type of the local variable that uses this local variable slot at the given bytecode offset. 130 */ 131 public Type getType(final int offset) { 132 final Range range = lookup(offset); 133 return range != null ? range.type : null; 134 } 135 136 /** 137 * Returns the range covering the given bytecode offset, or {@code null} if no range covers it. Since the stored ranges never overlap, only the range 138 * with the greatest start offset at or below the given offset can cover it. 139 */ 140 private Range lookup(final int offset) { 141 final Map.Entry<Integer, Range> entry = ranges.floorEntry(Integer.valueOf(offset)); 142 return entry != null && entry.getValue().end >= offset ? entry.getValue() : null; 143 } 144}