• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.res.android;
2 
3 // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp
4 //   and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/include/androidfw/ResourceTypes.h
5 
6 import static org.robolectric.res.android.Errors.BAD_TYPE;
7 import static org.robolectric.res.android.Errors.NAME_NOT_FOUND;
8 import static org.robolectric.res.android.Errors.NO_ERROR;
9 import static org.robolectric.res.android.Errors.NO_INIT;
10 import static org.robolectric.res.android.ResourceString.decodeString;
11 import static org.robolectric.res.android.ResourceTypes.validate_chunk;
12 import static org.robolectric.res.android.Util.ALOGI;
13 import static org.robolectric.res.android.Util.ALOGW;
14 import static org.robolectric.res.android.Util.SIZEOF_INT;
15 import static org.robolectric.res.android.Util.isTruthy;
16 
17 import java.lang.ref.WeakReference;
18 import java.nio.ByteBuffer;
19 import java.nio.ByteOrder;
20 import java.util.Objects;
21 import org.robolectric.res.android.ResourceString.Type;
22 import org.robolectric.res.android.ResourceTypes.ResChunk_header;
23 import org.robolectric.res.android.ResourceTypes.ResStringPool_header;
24 import org.robolectric.res.android.ResourceTypes.ResStringPool_header.Writer;
25 import org.robolectric.res.android.ResourceTypes.ResStringPool_ref;
26 import org.robolectric.res.android.ResourceTypes.ResStringPool_span;
27 import org.robolectric.res.android.ResourceTypes.WithOffset;
28 
29 /**
30  * Convenience class for accessing data in a ResStringPool resource.
31  */
32 @SuppressWarnings("NewApi")
33 public class ResStringPool {
34 
35   private static boolean kDebugStringPoolNoisy = false;
36 
37   private final long myNativePtr;
38 
39   private int                    mError;
40 
41    byte[]                       mOwnedData;
42   //private Object mOwnedData;
43 
44   private ResStringPool_header mHeader;
45   private int                      mSize;
46 //    private mutable Mutex               mDecodeLock;
47 //    const uint32_t*             mEntries;
48   private IntArray             mEntries;
49 //    const uint32_t*             mEntryStyles;
50     private IntArray             mEntryStyles;
51 //    const void*                 mStrings;
52     private int                 mStrings;
53   //private List<String> mStrings;
54   //private String[] mCache;
55   //private char16_t mutable**          mCache;
56     private int                    mStringPoolSize;    // number of uint16_t
57 //    const uint32_t*             mStyles;
58     private int             mStyles;
59     private int                    mStylePoolSize;    // number of int
60 
ResStringPool()61   public ResStringPool() {
62     mError = NO_INIT;
63     myNativePtr = Registries.NATIVE_STRING_POOLS.register(new WeakReference<>(this));
64   }
65 
66   @Override
finalize()67   protected void finalize() throws Throwable {
68     Registries.NATIVE_STRING_POOLS.unregister(myNativePtr);
69   }
70 
getNativePtr()71   public long getNativePtr() {
72     return myNativePtr;
73   }
74 
getNativeObject(long nativeId)75   public static ResStringPool getNativeObject(long nativeId) {
76     return Registries.NATIVE_STRING_POOLS.getNativeObject(nativeId).get();
77   }
78 
79   static class IntArray extends WithOffset {
IntArray(ByteBuffer buf, int offset)80     IntArray(ByteBuffer buf, int offset) {
81       super(buf, offset);
82     }
83 
get(int idx)84     int get(int idx) {
85       return myBuf().getInt(myOffset() + idx * SIZEOF_INT);
86     }
87   }
88 
setToEmpty()89   void setToEmpty()
90   {
91     uninit();
92 
93     ByteBuffer buf = ByteBuffer.allocate(16 * 1024).order(ByteOrder.LITTLE_ENDIAN);
94     Writer resStringPoolWriter = new Writer();
95     resStringPoolWriter.write(buf);
96     mOwnedData = new byte[buf.position()];
97     buf.position();
98     buf.get(mOwnedData);
99 
100     ResStringPool_header header = new ResStringPool_header(buf, 0);
101     mSize = 0;
102     mEntries = null;
103     mStrings = 0;
104     mStringPoolSize = 0;
105     mEntryStyles = null;
106     mStyles = 0;
107     mStylePoolSize = 0;
108     mHeader = header;
109   }
110 
111   //  status_t setTo(const void* data, size_t size, bool copyData=false);
setTo(ByteBuffer buf, int offset, int size, boolean copyData)112   public int setTo(ByteBuffer buf, int offset, int size, boolean copyData) {
113     if (!isTruthy(buf) || !isTruthy(size)) {
114       return (mError=BAD_TYPE);
115     }
116 
117     uninit();
118 
119     // The chunk must be at least the size of the string pool header.
120     if (size < ResStringPool_header.SIZEOF) {
121       ALOGW("Bad string block: data size %zu is too small to be a string block", size);
122       return (mError=BAD_TYPE);
123     }
124 
125     // The data is at least as big as a ResChunk_header, so we can safely validate the other
126     // header fields.
127     // `data + size` is safe because the source of `size` comes from the kernel/filesystem.
128     if (validate_chunk(new ResChunk_header(buf, offset), ResStringPool_header.SIZEOF,
129         size,
130         "ResStringPool_header") != NO_ERROR) {
131       ALOGW("Bad string block: malformed block dimensions");
132       return (mError=BAD_TYPE);
133     }
134 
135 //    final boolean notDeviceEndian = htods((short) 0xf0) != 0xf0;
136 //
137 //    if (copyData || notDeviceEndian) {
138 //      mOwnedData = data;
139 //      if (mOwnedData == null) {
140 //        return (mError=NO_MEMORY);
141 //      }
142 ////      memcpy(mOwnedData, data, size);
143 //      data = mOwnedData;
144 //    }
145 
146     // The size has been checked, so it is safe to read the data in the ResStringPool_header
147     // data structure.
148     mHeader = new ResStringPool_header(buf, offset);
149 
150 //    if (notDeviceEndian) {
151 //      ResStringPool_header h = final_cast<ResStringPool_header*>(mHeader);
152 //      h.header.headerSize = dtohs(mHeader.header.headerSize);
153 //      h.header.type = dtohs(mHeader.header.type);
154 //      h.header.size = dtohl(mHeader.header.size);
155 //      h.stringCount = dtohl(mHeader.stringCount);
156 //      h.styleCount = dtohl(mHeader.styleCount);
157 //      h.flags = dtohl(mHeader.flags);
158 //      h.stringsStart = dtohl(mHeader.stringsStart);
159 //      h.stylesStart = dtohl(mHeader.stylesStart);
160 //    }
161 
162     if (mHeader.header.headerSize > mHeader.header.size
163         || mHeader.header.size > size) {
164       ALOGW("Bad string block: header size %d or total size %d is larger than data size %d\n",
165           (int)mHeader.header.headerSize, (int)mHeader.header.size, (int)size);
166       return (mError=BAD_TYPE);
167     }
168     mSize = mHeader.header.size;
169     mEntries = new IntArray(mHeader.myBuf(), mHeader.myOffset() + mHeader.header.headerSize);
170 
171     if (mHeader.stringCount > 0) {
172       if ((mHeader.stringCount*4 /*sizeof(uint32_t)*/ < mHeader.stringCount)  // uint32 overflow?
173           || (mHeader.header.headerSize+(mHeader.stringCount*4 /*sizeof(uint32_t)*/))
174           > size) {
175         ALOGW("Bad string block: entry of %d items extends past data size %d\n",
176             (int)(mHeader.header.headerSize+(mHeader.stringCount*4/*sizeof(uint32_t)*/)),
177             (int)size);
178         return (mError=BAD_TYPE);
179       }
180 
181       int charSize;
182       if (isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG)) {
183         charSize = 1 /*sizeof(uint8_t)*/;
184       } else {
185         charSize = 2 /*sizeof(uint16_t)*/;
186       }
187 
188       // There should be at least space for the smallest string
189       // (2 bytes length, null terminator).
190       if (mHeader.stringsStart >= (mSize - 2 /*sizeof(uint16_t)*/)) {
191         ALOGW("Bad string block: string pool starts at %d, after total size %d\n",
192             (int)mHeader.stringsStart, (int)mHeader.header.size);
193         return (mError=BAD_TYPE);
194       }
195 
196       mStrings = mHeader.stringsStart;
197 
198       if (mHeader.styleCount == 0) {
199         mStringPoolSize = (mSize - mHeader.stringsStart) / charSize;
200       } else {
201         // check invariant: styles starts before end of data
202         if (mHeader.stylesStart >= (mSize - 2 /*sizeof(uint16_t)*/)) {
203           ALOGW("Bad style block: style block starts at %d past data size of %d\n",
204               (int)mHeader.stylesStart, (int)mHeader.header.size);
205           return (mError=BAD_TYPE);
206         }
207         // check invariant: styles follow the strings
208         if (mHeader.stylesStart <= mHeader.stringsStart) {
209           ALOGW("Bad style block: style block starts at %d, before strings at %d\n",
210               (int)mHeader.stylesStart, (int)mHeader.stringsStart);
211           return (mError=BAD_TYPE);
212         }
213         mStringPoolSize =
214             (mHeader.stylesStart-mHeader.stringsStart)/charSize;
215       }
216 
217       // check invariant: stringCount > 0 requires a string pool to exist
218       if (mStringPoolSize == 0) {
219         ALOGW("Bad string block: stringCount is %d but pool size is 0\n", (int)mHeader.stringCount);
220         return (mError=BAD_TYPE);
221       }
222 
223       //      if (notDeviceEndian) {
224       //        int i;
225       //        uint32_t* e = final_cast<uint32_t*>(mEntries);
226       //        for (i=0; i<mHeader.stringCount; i++) {
227       //          e[i] = dtohl(mEntries[i]);
228       //        }
229       //        if (!(mHeader.flags&ResStringPool_header::UTF8_FLAG)) {
230       //                final uint16_t* strings = (final uint16_t*)mStrings;
231       //          uint16_t* s = final_cast<uint16_t*>(strings);
232       //          for (i=0; i<mStringPoolSize; i++) {
233       //            s[i] = dtohs(strings[i]);
234       //          }
235       //        }
236       //      }
237 
238       //      if ((mHeader->flags&ResStringPool_header::UTF8_FLAG &&
239       //          ((uint8_t*)mStrings)[mStringPoolSize-1] != 0) ||
240       //      (!(mHeader->flags&ResStringPool_header::UTF8_FLAG) &&
241       //          ((uint16_t*)mStrings)[mStringPoolSize-1] != 0)) {
242 
243       if ((isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG)
244               && (mHeader.getByte(mStrings + mStringPoolSize - 1) != 0))
245           || (!isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG)
246               && (mHeader.getShort(mStrings + mStringPoolSize * 2 - 2) != 0))) {
247         ALOGW("Bad string block: last string is not 0-terminated\n");
248         return (mError=BAD_TYPE);
249       }
250     } else {
251       mStrings = -1;
252       mStringPoolSize = 0;
253     }
254 
255     if (mHeader.styleCount > 0) {
256       mEntryStyles = new IntArray(mEntries.myBuf(), mEntries.myOffset() + mHeader.stringCount * SIZEOF_INT);
257       // invariant: integer overflow in calculating mEntryStyles
258       if (mEntryStyles.myOffset() < mEntries.myOffset()) {
259         ALOGW("Bad string block: integer overflow finding styles\n");
260         return (mError=BAD_TYPE);
261       }
262 
263 //      if (((const uint8_t*)mEntryStyles-(const uint8_t*)mHeader) > (int)size) {
264       if ((mEntryStyles.myOffset() - mHeader.myOffset()) > (int)size) {
265         ALOGW("Bad string block: entry of %d styles extends past data size %d\n",
266             (int)(mEntryStyles.myOffset()),
267         (int)size);
268         return (mError=BAD_TYPE);
269       }
270       mStyles = mHeader.stylesStart;
271       if (mHeader.stylesStart >= mHeader.header.size) {
272         ALOGW("Bad string block: style pool starts %d, after total size %d\n",
273             (int)mHeader.stylesStart, (int)mHeader.header.size);
274         return (mError=BAD_TYPE);
275       }
276       mStylePoolSize =
277           (mHeader.header.size-mHeader.stylesStart) /* / sizeof(uint32_t)*/;
278 
279 //      if (notDeviceEndian) {
280 //        size_t i;
281 //        uint32_t* e = final_cast<uint32_t*>(mEntryStyles);
282 //        for (i=0; i<mHeader.styleCount; i++) {
283 //          e[i] = dtohl(mEntryStyles[i]);
284 //        }
285 //        uint32_t* s = final_cast<uint32_t*>(mStyles);
286 //        for (i=0; i<mStylePoolSize; i++) {
287 //          s[i] = dtohl(mStyles[i]);
288 //        }
289 //      }
290 
291 //        final ResStringPool_span endSpan = {
292 //          { htodl(ResStringPool_span.END) },
293 //          htodl(ResStringPool_span.END), htodl(ResStringPool_span.END)
294 //      };
295 //      if (memcmp(&mStyles[mStylePoolSize-(sizeof(endSpan)/sizeof(uint32_t))],
296 //                   &endSpan, sizeof(endSpan)) != 0) {
297       ResStringPool_span endSpan = new ResStringPool_span(buf,
298           mHeader.myOffset() + mStyles + (mStylePoolSize - ResStringPool_span.SIZEOF /* / 4 */));
299       if (!endSpan.isEnd()) {
300         ALOGW("Bad string block: last style is not 0xFFFFFFFF-terminated\n");
301         return (mError=BAD_TYPE);
302       }
303     } else {
304       mEntryStyles = null;
305       mStyles = 0;
306       mStylePoolSize = 0;
307     }
308 
309     return (mError=NO_ERROR);
310   }
311 
312 //  public void setTo(XmlResStringPool xmlStringPool) {
313 //    this.mHeader = new ResStringPoolHeader();
314 //    this.mStrings = new ArrayList<>();
315 //    Collections.addAll(mStrings, xmlStringPool.strings());
316 //  }
317 
setError(int error)318   private int setError(int error) {
319     mError = error;
320     return mError;
321   }
322 
uninit()323   void uninit() {
324     setError(NO_INIT);
325     mHeader = null;
326   }
327 
stringAt(int idx)328   public String stringAt(int idx) {
329     if (mError == NO_ERROR && idx < mHeader.stringCount) {
330         final boolean isUTF8 = (mHeader.flags&ResStringPool_header.UTF8_FLAG) != 0;
331 //        const uint32_t off = mEntries[idx]/(isUTF8?sizeof(uint8_t):sizeof(uint16_t));
332       ByteBuffer buf = mHeader.myBuf();
333       int bufOffset = mHeader.myOffset();
334       // const uint32_t off = mEntries[idx]/(isUTF8?sizeof(uint8_t):sizeof(uint16_t));
335       final int off = mEntries.get(idx)
336             /(isUTF8?1/*sizeof(uint8_t)*/:2/*sizeof(uint16_t)*/);
337       if (off < (mStringPoolSize-1)) {
338         if (!isUTF8) {
339           final int strings = mStrings;
340           final int str = strings+off*2;
341           return decodeString(buf, bufOffset + str, Type.UTF16);
342 //          int u16len = decodeLengthUTF16(buf, bufOffset + str);
343 //          if ((str+u16len*2-strings) < mStringPoolSize) {
344 //            // Reject malformed (non null-terminated) strings
345 //            if (buf.getShort(bufOffset + str + u16len*2) != 0x0000) {
346 //              ALOGW("Bad string block: string #%d is not null-terminated",
347 //                  (int)idx);
348 //              return null;
349 //            }
350 //            byte[] bytes = new byte[u16len * 2];
351 //            buf.position(bufOffset + str);
352 //            buf.get(bytes);
353 //               // Reject malformed (non null-terminated) strings
354 //               if (str[encLen] != 0x00) {
355 //                   ALOGW("Bad string block: string #%d is not null-terminated",
356 //                         (int)idx);
357 //                   return NULL;
358 //               }
359 //            return new String(bytes, StandardCharsets.UTF_16);
360 //          } else {
361 //            ALOGW("Bad string block: string #%d extends to %d, past end at %d\n",
362 //                (int)idx, (int)(str+u16len-strings), (int)mStringPoolSize);
363 //          }
364         } else {
365           final int strings = mStrings;
366           final int u8str = strings+off;
367           return decodeString(buf, bufOffset + u8str, Type.UTF8);
368 
369 //                *u16len = decodeLength(&u8str);
370 //          size_t u8len = decodeLength(&u8str);
371 //
372 //          // encLen must be less than 0x7FFF due to encoding.
373 //          if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) {
374 //            AutoMutex lock(mDecodeLock);
375 //
376 //            if (mCache != NULL && mCache[idx] != NULL) {
377 //              return mCache[idx];
378 //            }
379 //
380 //            // Retrieve the actual length of the utf8 string if the
381 //            // encoded length was truncated
382 //            if (stringDecodeAt(idx, u8str, u8len, &u8len) == NULL) {
383 //                return NULL;
384 //            }
385 //
386 //            // Since AAPT truncated lengths longer than 0x7FFF, check
387 //            // that the bits that remain after truncation at least match
388 //            // the bits of the actual length
389 //            ssize_t actualLen = utf8_to_utf16_length(u8str, u8len);
390 //            if (actualLen < 0 || ((size_t)actualLen & 0x7FFF) != *u16len) {
391 //              ALOGW("Bad string block: string #%lld decoded length is not correct "
392 //                  "%lld vs %llu\n",
393 //                  (long long)idx, (long long)actualLen, (long long)*u16len);
394 //              return NULL;
395 //            }
396 //
397 //            utf8_to_utf16(u8str, u8len, u16str, *u16len + 1);
398 //
399 //            if (mCache == NULL) {
400 // #ifndef __ANDROID__
401 //                if (kDebugStringPoolNoisy) {
402 //                    ALOGI("CREATING STRING CACHE OF %zu bytes",
403 //                          mHeader->stringCount*sizeof(char16_t**));
404 //                }
405 // #else
406 //                // We do not want to be in this case when actually running Android.
407 //                ALOGW("CREATING STRING CACHE OF %zu bytes",
408 //                        static_cast<size_t>(mHeader->stringCount*sizeof(char16_t**)));
409 // #endif
410 //                mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t*));
411 //                if (mCache == NULL) {
412 //                    ALOGW("No memory trying to allocate decode cache table of %d bytes\n",
413 //                          (int)(mHeader->stringCount*sizeof(char16_t**)));
414 //                    return NULL;
415 //                }
416 //            }
417 //            *u16len = (size_t) actualLen;
418 //            char16_t *u16str = (char16_t *)calloc(*u16len+1, sizeof(char16_t));
419 //            if (!u16str) {
420 //              ALOGW("No memory when trying to allocate decode cache for string #%d\n",
421 //                  (int)idx);
422 //              return NULL;
423 //            }
424 //
425 //            if (kDebugStringPoolNoisy) {
426 //              ALOGI("Caching UTF8 string: %s", u8str);
427 //            }
428 //
429 //            mCache[idx] = u16str;
430 //            return u16str;
431 //          } else {
432 //            ALOGW("Bad string block: string #%lld extends to %lld, past end at %lld\n",
433 //                (long long)idx, (long long)(u8str+u8len-strings),
434 //                (long long)mStringPoolSize);
435 //          }
436         }
437       } else {
438         ALOGW("Bad string block: string #%d entry is at %d, past end at %d\n",
439             (int)idx, (int)(off*2/*sizeof(uint16_t)*/),
440             (int)(mStringPoolSize*2/*sizeof(uint16_t)*/));
441       }
442     }
443     return null;
444   }
445 
stringAt(int idx, Ref<Integer> outLen)446   String stringAt(int idx, Ref<Integer> outLen) {
447     String s = stringAt(idx);
448     if (s != null && outLen != null) {
449       outLen.set(s.length());
450     }
451     return s;
452   }
453 
string8At(int id, Ref<Integer> outLen)454   public String string8At(int id, Ref<Integer> outLen) {
455     return stringAt(id, outLen);
456   }
457 
styleAt(final ResStringPool_ref ref)458   final ResStringPool_span styleAt(final ResStringPool_ref ref) {
459     return styleAt(ref.index);
460   }
461 
styleAt(int idx)462   public final ResStringPool_span styleAt(int idx) {
463     if (mError == NO_ERROR && idx < mHeader.styleCount) {
464       // const uint32_t off = (mEntryStyles[idx]/sizeof(uint32_t));
465       final int off = mEntryStyles.get(idx) / SIZEOF_INT;
466       if (off < mStylePoolSize) {
467         // return (const ResStringPool_span*)(mStyles+off);
468         return new ResStringPool_span(
469             mHeader.myBuf(), mHeader.myOffset() + mStyles + off * SIZEOF_INT);
470       } else {
471         ALOGW("Bad string block: style #%d entry is at %d, past end at %d\n",
472             (int)idx, (int)(off*SIZEOF_INT),
473             (int)(mStylePoolSize*SIZEOF_INT));
474       }
475     }
476     return null;
477   }
478 
indexOfString(String str)479   public int indexOfString(String str) {
480     if (mError != NO_ERROR) {
481       return mError;
482     }
483 
484     if (kDebugStringPoolNoisy) {
485       ALOGI("indexOfString : %s", str);
486     }
487 
488     if ( (mHeader.flags&ResStringPoolHeader.SORTED_FLAG) != 0) {
489       // Do a binary search for the string...  this is a little tricky,
490       // because the strings are sorted with strzcmp16().  So to match
491       // the ordering, we need to convert strings in the pool to UTF-16.
492       // But we don't want to hit the cache, so instead we will have a
493       // local temporary allocation for the conversions.
494       int l = 0;
495       int h = mHeader.stringCount-1;
496 
497       int mid;
498       while (l <= h) {
499         mid = l + (h - l)/2;
500         String s = stringAt(mid);
501         int c = s != null ? s.compareTo(str) : -1;
502         if (kDebugStringPoolNoisy) {
503           ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n",
504               s, c, (int)l, (int)mid, (int)h);
505         }
506         if (c == 0) {
507           if (kDebugStringPoolNoisy) {
508             ALOGI("MATCH!");
509           }
510           return mid;
511         } else if (c < 0) {
512           l = mid + 1;
513         } else {
514           h = mid - 1;
515         }
516       }
517     } else {
518       // It is unusual to get the ID from an unsorted string block...
519       // most often this happens because we want to get IDs for style
520       // span tags; since those always appear at the end of the string
521       // block, start searching at the back.
522       for (int i = mHeader.stringCount; i>=0; i--) {
523         String s = stringAt(i);
524         if (kDebugStringPoolNoisy) {
525           ALOGI("Looking at %s, i=%d\n", s, i);
526         }
527         if (Objects.equals(s, str)) {
528           if (kDebugStringPoolNoisy) {
529             ALOGI("MATCH!");
530           }
531           return i;
532         }
533       }
534     }
535 
536     return NAME_NOT_FOUND;
537   }
538 //
size()539     public int size() {
540       return mError == NO_ERROR ? mHeader.stringCount : 0;
541     }
542 
styleCount()543     int styleCount() {
544       return mError == NO_ERROR ? mHeader.styleCount : 0;
545     }
546 
bytes()547     int bytes() {
548       return mError == NO_ERROR ? mHeader.header.size : 0;
549     }
550 
isUTF8()551   public boolean isUTF8() {
552     return true;
553   }
554 
getError()555   public int getError() {
556     return mError;
557   }
558 
559 //    int styleCount() final;
560 //    int bytes() final;
561 //
562 //    boolean isSorted() final;
563 //    boolean isUTF8() final;
564 //
565 
566 }
567