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